Next.js GitHub Actions 生产部署流水线:自动 patch、迁移与部署通知
从 Jenkins 到 GitHub Actions:一人产品的 Next.js 生产部署流水线实战
本文基于一个真实的 Next.js 全栈项目(相册 / SaaS 类应用)的 CI/CD 改造经验整理,已脱敏:不含服务器 IP、密钥内容、内网地址或任何可复用的凭证。目标是给「一个人做产品、自托管 VPS、想少踩坑」的开发者一份可照抄架构、自行填配置的参考。
背景:我们之前怎么部署?
早期方案是 Jenkins + 本地触发脚本:
- 开发者在本地跑
deploy.sh,脚本负责 lint、build、打 tar 包、SCP 上传、远端 PM2 重启 - Jenkins 只是「远程按钮」,和 Git 没有强绑定
- 部署记录散落在本地日志目录,很难回答「生产现在跑的是哪个 commit?」
随着功能迭代加速(体验官运营闭环、账户页改版等),这套方式的短板越来越明显:
- merge 不等于上线 — 容易忘记手动部署
- 数据库迁移靠记忆 — 某次忘了
prisma migrate deploy,线上 schema 滞后 - 没有统一的部署通知 — 成功了没人知道,失败了更没人知道
- 版本号对用户和运营不可见 — 排障时要 SSH 上去查
.deploy-commit
于是我们做了三件事:用 GitHub Actions 替代 Jenkins、补齐部署后链路(迁移 / 版本 / 通知)、在账户页低调展示版本号。
总体架构:双轨 CI/CD
| 轨道 | 触发 | 职责 |
|---|---|---|
| CI | PR → main | 质量门禁:pnpm lint + pnpm build,不部署 |
| CD | push main | 自动 patch、构建、上传、迁移、重启、通知 |
PR 阶段只验证「能不能合」,合进去才动生产——这是小团队最省心的分界。
生产部署 Workflow 分步拆解
1. 跳过条件:避免无限循环
merge 后会自动 bump 版本并 再 push 一个 commit(chore(version): bump patch to x.y.z [skip ci])。因此 Workflow 必须跳过两类提交:
- commit message 含
[skip ci] - message 以
chore(version):开头
否则会出现:部署 → bump commit → 再次触发部署 → 再 bump 的死循环。
2. 自动 patch 版本(semver 第三位)
策略:每次 merge main,patch +1;minor/major 仍由人工发版。
Workflow 顺序是:
- 先 bump(工作区
package.json变为新版本,尚未 commit) - 再 build + deploy(线上跑的是新版本号)
- 部署成功后 由
github-actions[bot]commit + push bump 结果,并带[skip ci]
这样 Git 历史里能清楚看到「哪次部署对应哪个 semver」,同时不会重复触发流水线。
3. Secrets 与 Variables(脱敏清单)
Secrets(敏感,不进仓库):
| 名称 | 用途 |
|---|---|
PROD_SSH_PRIVATE_KEY | 部署用 SSH 私钥 |
PROD_ENV_FILE | 生产环境完整 .env 内容 |
OPC_FEED_API_TOKEN | OPC Feed REST/MCP 个人 API 令牌 |
Variables(非敏感,可公开配置名):
| 名称 | 用途 |
|---|---|
LIFEFRAME_PROD_URL | 生产站点 URL(health / 通知链接) |
OPC_FEED_API_URL | OPC API 根地址 |
OPC_DEPLOY_PARENT_EVENT_ID | 部署事件的父节点 ID(时间线分组) |
Runner 上通过 SSH config 挂载私钥,不要在日志里 echo 密钥;.env 由 Secret 一次性写入工作区,随 tar 包带到服务器。
4. deploy.sh:一条脚本打通本地与 CI
核心思路:GitHub Actions 和开发者本地共用同一个 deploy.sh,避免「CI 一套、手工又一套」。
本地阶段:
pnpm install→pnpm lint(可配置失败是否中止)→pnpm build- 构建前注入版本信息:
- 将
.next、package.json、prisma/等打入 tar.gz
远端阶段(SSH 远程 bash):
- 备份旧
.next,解压新包 - 写入
.deploy-commit(供/api/health读取) pnpm install --prod --frozen-lockfilepnpm prisma migrate deploy← 本次改造的关键补齐pnpm prisma generate- PM2 重启应用 + 三个后台 worker
curl /api/health轮询,超时则失败退出
migrate 失败即中止、不重启 PM2,避免「半新半旧」的灾难状态。
5. 构建 OOM 防护
GitHub-hosted runner 默认 Node 堆约 2GB,Next.js 生产 build + Sentry source map 容易 OOM。我们在 CI 里显式设置:
这是「能稳定跑完 6 分钟部署」的实际经验,不是过度优化。
账户页版本号:低调但有用
产品决策:只在 /account 底部显示,不对公开落地页打扰。
数据来源:
NEXT_PUBLIC_APP_VERSION— build 时从package.json注入NEXT_PUBLIC_GIT_COMMIT— build 时从git rev-parse --short HEAD注入- 本地 dev 回退为
v0.2.x · dev(在next.config.js的env字段配置)
用户截图账户页底部,客服/开发就能立刻对齐「他看到的是不是最新部署」。
服务端 /api/health 仍返回 { ok: true, commit }(读 .deploy-commit 或 DEPLOY_GIT_COMMIT),供脚本探活,不依赖数据库。
OPC Feed:部署结果写进时间线 + 推 IM
OPC Feed 是我们另一个项目——面向 AI Agent 与一人公司的项目时间线。这次把它接进部署通知,而不是再造一套 Slack/飞书 Webhook。
为什么用 OPC 而不是裸 Webhook?
| 能力 | 裸 Webhook | OPC Feed |
|---|---|---|
| 即时 IM | ✅ | ✅(钉钉/飞书/企微等) |
| 可追溯时间线 | ❌ | ✅ 按项目归档 |
| 与开发记录同屏 | ❌ | ✅ 和里程碑、决策放一起 |
| Agent 可读上下文 | ❌ | ✅ MCP + REST |
实现方式
CI 末尾增加一步(continue-on-error: true,通知失败不应掩盖部署成败):
要点:
typeSlug: deployment— 平台内置「部署」类型,和 OPC 自家 prod 流水线一致notifyOnCreate: true— 成功也推 IM(我们选的策略;也可改为仅失败推送)- 父事件 — 先手动建一条「XX 生产部署跟踪」,后续每次部署挂为子事件,时间线不散
- Token 缺失时
exit 0— 不影响部署主路径,方便 Fork 或未配置 OPC 的环境
失败分支用 if: failure() 再跑同一脚本,传 DEPLOY_STATUS=failure,importance 更高、标题带 ❌。
一次完整 merge 的时间线
与 Jenkins 退役的对比
| 维度 | Jenkins 时代 | GitHub Actions 现在 |
|---|---|---|
| 触发 | 手动 / 定时 | merge main 自动 |
| 与 Git 关系 | 弱 | 强(SHA、PR、Workflow run 链接) |
| 版本号 | 无自动 bump | patch 自动 + 回写 commit |
| DB 迁移 | 靠人工 | migrate deploy 内置 |
| 通知 | 无 | OPC 时间线 + IM |
| 运维成本 | 需维护 Jenkins 实例 | 零额外服务器 |
Jenkinsfile 删除后,部署文档归档到 docs/archive/deployment/,主路径文档指向 GitHub Actions 指南。
你可以直接抄走的 Checklist
基础设施
- 生产机 SSH 密钥对 + 仅允许 GitHub Actions IP 或 bastion
- Secrets:
PROD_SSH_PRIVATE_KEY、PROD_ENV_FILE - VPS 安装 Node 22、pnpm、PM2、curl
Workflow
- PR:
ci.ymllint + build - main:
deploy-prod.yml+[skip ci]防循环 -
permissions: contents: write用于 bump commit
deploy.sh
- 构建注入
NEXT_PUBLIC_*版本 env - 远端
prisma migrate deploy在 generate 之前 - health check 失败则 exit 1
可观测性
-
/api/health返回 commit - 账户页或设置页版本 badge
- OPC / Webhook 部署通知(
continue-on-error: true)
常见坑
- bump commit 触发二次部署 — 必须
[skip ci]+ Workflowif过滤chore(version): - migrate 在 PM2 重启之后 — 顺序错了会出现新代码连旧 schema
- 通知 step 失败导致整个 Workflow 红 — 用
continue-on-error: true - 把 Secret 写进日志 — SSH key、
.env只通过 env 注入,禁止 echo - Runner OOM — Next.js 生产 build 建议 4GB 堆 + 关 source map 上传
小结
这次改造的本质不是「换成 GitHub Actions 更潮」,而是把一人产品最缺的四件事补上了:
- merge 即部署 — 减少人为遗漏
- 迁移自动化 — 减少 schema 漂移
- 版本可读 — 用户、运营、开发对齐同一事实
- 部署可感知 — 时间线 + IM,成功失败都有记录
如果你也在维护 Next.js + 自托管 + Prisma 的项目,可以直接复用「双轨 Workflow + 单脚本 deploy.sh + health 探活 + 可选 OPC 通知」这套骨架,把 Secrets 和域名换成自己的即可。
本文对应开源仓库的一次 CI/CD 改造(Jenkins 退役 + 自动 patch + OPC 部署通知 + 账户页版本号)。架构与脚本思路可复用;具体 IP、密钥与内网配置请自行替换,切勿在公开场合粘贴生产 Secret。