静态博客的发布流程本应是「写完、push、完事」,但很多人还在手动 npm run build、手动 rsync 上传——每次发文都多一步出错的机会。GitHub Actions 可以把这条链路完全自动化:push 到主分支后,构建、部署、通知全部自动完成。本文以一个 Node 系静态站(Astro/Hugo 同理)为例走通全流程。
工作流全貌
push 到 main │ ▼GitHub Actions Runner 启动 ├─ 1. 拉代码 ├─ 2. 装依赖、构建静态产物 ├─ 3. 部署产物(rsync 到服务器 / 推到 Pages) └─ 4. 失败时通知工作流文件放在仓库的 .github/workflows/deploy.yml:
name: Deploy Blog
on: push: branches: [main] workflow_dispatch: # 允许在网页上手动触发
jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: 拉取代码 uses: actions/checkout@v4
- name: 安装 Node uses: actions/setup-node@v4 with: node-version: 22 cache: npm
- name: 安装依赖并构建 run: | npm ci npm run build
- name: 部署到服务器 with: switches: -avzr --delete path: dist/ remote_path: /var/www/blog/ remote_host: ${{ secrets.DEPLOY_HOST }} remote_user: ${{ secrets.DEPLOY_USER }} remote_key: ${{ secrets.DEPLOY_KEY }}逐段拆解
触发器 on:push: branches: [main] 表示推送到 main 分支时触发;workflow_dispatch 加一个手动按钮,改完配置想立刻验证时不用特意造一个 commit。
npm ci 而不是 npm install:CI 环境的铁律。ci 严格按 package-lock.json 安装,保证每次构建依赖版本完全一致;install 可能按 semver 范围悄悄升级某个包,让「本地能过、CI 挂掉」变成悬案。搭配 setup-node 的 cache: npm,依赖下载在多次运行间缓存,构建快很多。
部署环节的安全:服务器地址、用户名、SSH 私钥全部放仓库的 Settings → Secrets and variables → Actions 里,以 ${{ secrets.XXX }} 引用。私钥内容绝不能写进 YAML——这个文件在仓库里是公开的。
--delete 参数让 rsync 删除服务器上多余的旧文件,保持目录与构建产物一致;它对部署目录是安全的,但务必确认 remote_path 没填错——填成 /var/www/ 会把兄弟站点的目录清掉。
为部署准备专用 SSH 密钥
不要在多个地方复用同一把私钥,给 Actions 单独生成一对:
ssh-keygen -t ed25519 -C "github-actions-blog" -f ~/.ssh/gh-actions-deploy公钥追加到服务器部署用户的 ~/.ssh/authorized_keys,私钥全文粘贴到仓库 Secret DEPLOY_KEY。这样即便 CI 侧泄露,也只暴露这一把受限密钥,可以随时吊销更换。更严格的做法是在服务器上给该用户配置 command= 前缀限制它只能执行 rsync。
不想维护服务器?推到 GitHub Pages
如果博客就托管在 GitHub Pages,部署步骤换成官方 Action,连服务器和密钥都不用管:
- name: 配置 Pages uses: actions/configure-pages@v5
- name: 上传产物 uses: actions/upload-pages-artifact@v3 with: path: dist/
- name: 部署到 Pages id: deployment uses: actions/deploy-pages@v4(需要在仓库 Settings → Pages 里把来源选为 GitHub Actions。)
让失败可见
流水线最大的谎言是「绿钩即一切正常」,第二大的谎言是「失败了会有人发现」。加一步失败通知,比如用 curl 调用任意 webhook(Server 酱、Telegram Bot、企业微信均可):
- name: 失败通知 if: failure() run: | curl -s "https://api.day.app/${{ secrets.BARK_KEY }}/博客部署失败/${{ github.sha }}?level=critical"if: failure() 表示仅当前面步骤失败才执行。同理 if: success() 可以发成功确认——初期建议两种都开,跑稳定后只留失败通知,避免消息疲劳。
值得加的两个增强
- 构建前内容校验:在 build 步骤前加
npm run check(如果项目提供),让 Front Matter 缺字段这类问题在 CI 阶段就拦住,而不是部署后才发现页面异常。 - 并发控制:连续 push 两个 commit 时不希望两个部署任务打架:
concurrency: group: deploy cancel-in-progress: true同一时刻只允许一个部署在跑,新的 push 会取消未完成的旧任务——反正部署的目标是「最新状态」,中间的半成品没有意义。
延伸阅读
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 4 天
部分内容可能已过时