GitHub Actions 自动部署:push 之后什么都不用管

手动 rsync 上传能用,但每次改完还要敲命令,迟早会忘。用 GitHub Actions 把这件事自动化:push 到 main 就自动部署。

一、基本原理

bash
本地 push → GitHub 收到 → 起一台虚拟机构建 → 通过 SSH 把产物推到你的服务器

你需要提供三样东西:构建脚本、服务器地址、登录凭证(放 Secret,不进代码库)。

二、准备 SSH 密钥

不要用你自己的登录密钥,单独生成一对专供 CI 使用:

bash
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/gh_actions

# 公钥放到服务器
ssh-copy-id -i ~/.ssh/gh_actions.pub -p 2222 user@example.com

# 私钥内容填到 GitHub Secret
cat ~/.ssh/gh_actions

在仓库 Settings → Secrets and variables → Actions 里加三个 Secret:

Secret 名内容
SSH_PRIVATE_KEYgh_actions 私钥全文
SERVER_HOST服务器 IP 或域名
SERVER_USER登录用户名(如 baize)

三、部署到自己的服务器

.github/workflows/deploy.yml

yaml
name: deploy

on:
  push:
    branches: [ main ]
  workflow_dispatch:      # 允许手动触发,调试很有用

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 拉取代码
        uses: actions/checkout@v4

      - name: 部署到服务器
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 2222
          source: "./*"
          target: "/var/www/baize"
          strip_components: 0

      - name: 重载 Nginx
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 2222
          script: |
            sudo nginx -t && sudo systemctl reload nginx
            echo "deployed at $(date)"

如果服务器上有构建步骤(比如 npm build),改成 SSH 里执行:

yaml
      - name: 构建并重启
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 2222
          script: |
            cd /srv/app
            git pull --ff-only
            npm ci
            npm run build
            sudo systemctl restart myapp

四、部署到 GitHub Pages

yaml
name: deploy-pages
on:
  push:
    branches: [ main ]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/upload-pages-artifact@v3
        with:
          path: .
      - id: deployment
        uses: actions/deploy-pages@v4

五、加缓存,省时间省钱

yaml
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm          # 自动缓存 node_modules

      - run: npm ci
      - run: npm run build

六、只在特定路径变更时才部署

文档改了没必要重新部署站点:

yaml
on:
  push:
    branches: [ main ]
    paths:
      - "assets/**"
      - "index.html"
      - ".github/workflows/deploy.yml"

七、失败排查

现象排查
Permission denied (publickey)私钥格式不对(要含首尾行)、或公钥没放进服务器 authorized_keys
Host key verification failedscp-action 加 host_key 参数,或 ssh-action 加 disable_host_key_checking: true
命令找不到CI 里的 PATH 与登录 shell 不同,用绝对路径 /usr/bin/npm
部署成功但站点没变CDN 缓存,去 Cloudflare 清一下缓存

调试技巧:把敏感值打码后再 echo,确认 Secret 真的读到了:

yaml
      - name: 检查 Secret
        run: echo "host length: ${#SERVER_HOST}"
        env:
          SERVER_HOST: ${{ secrets.SERVER_HOST }}

八、安全注意

  1. Secret 永远不要 echo 出来,日志是公开的(私有仓库也一样,日志可能被分享);
  2. CI 专用密钥在服务器上限制权限:只在 authorized_keys 里允许它执行特定命令;
  3. 第三方 Action 尽量锁定版本或用 commit SHA,@v4 这种 tag 是可以被改的;
  4. fork 的 PR 默认拿不到 Secret,这个默认行为不要随便改。