第八篇:上云 — CI/CD 与自动化部署
进入信号:部署本身在浪费你的时间,或者出过一次生产事故——客户在等,你不在电脑前。
本篇解决的问题:怎么把代码从一个本地
npm run dev变成一个 7×24 可用的云服务?CI/CD 怎么从零搭建?多环境怎么管理?怎么选云平台?
那晚的事故
周六晚上 9 点,你在朋友家吃饭。G 发来消息:
「系统 502 了,明天周一要对账,今晚必须好。」
你打开手机,看不到日志,不知道发生了什么。你只能回复:「我在外面,大概一个半小时后到家,到了立刻修。」
G 回复:「好吧……」
那一个半小时你如坐针毡。到家后发现是 Supabase 连接池满了——10 个客户里有人导入了上万行的测试数据,把所有连接占满了。重启服务后恢复正常。但你永远忘不了 G 那句「好吧……」里的失望。
一次生产事故对客户信任的伤害,超过 10 次流畅体验建立的信任。 明天你再也不敢离开电脑了,但你也不可能 24 小时守在电脑前。
你需要让这个系统真正「上云」——不只是技术上部署到服务器,而是有一套机制让它持续可用。
第一步:环境体系搭建
直到现在,你只有两个环境:「本地开发(自己的笔记本)」和「生产环境(一台服务器)」。每次改完代码在本地测试,然后推到生产。这种模型一个人还能凑合,但已经开始出问题了:
- 你没法在「和生产环境相同的配置」下测试
- 一旦部署失败,客户直接受影响
- 你想让一个客户先试试新功能,但只能全量发布
你需要多环境:
本地开发 (Local) — 自己笔记本,随便折腾
↓ PR 提交
Preview 环境 (Preview) — 每个 Pull Request 自动部署,给客户预览
↓ 合并到 main
预发布环境 (Staging) — 和生产一模一样的环境,最后的验证
↓ 手动发布
生产环境 (Production) — 客户实际使用的环境
环境变量管理
每个环境有不同的数据库、不同的 API 密钥。你之前是把 .env 写死然后手动改。现在不能这样了:
# .env.local — 本地开发(不提交到 Git)
DATABASE_URL="postgresql://localhost:5432/reconciler_dev"
# .env.preview — Vercel Preview 环境变量
DATABASE_URL="postgresql://supabase:[email protected]:5432/reconciler_preview"
# .env.production — 生产环境
DATABASE_URL="postgresql://supabase:[email protected]:5432/reconciler_prod"
生产数据库和 Preview 数据库必须隔离——Preview 环境是用来测试的,上面会有脏数据、可能有破坏性操作。如果和生产共用数据库,一个测试脚本就能清掉客户的真实数据。
第二步:平台选型
你将现有代码从自己的一台 Linux 服务器,迁移到专业云平台。
前端:Vercel
你的前端是 Next.js,Vercel 是 Next.js 的母公司做的平台。
- 天然支持 Next.js 的 ISR/SSR/App Router
- 免费层够你撑到 10000 访问量/天
- 它接管了 CDN、SSL、自定义域名
操作步骤:
- 在 Vercel 导入 GitHub 仓库
- 配好环境变量
- 设置
main分支自动部署到 Production - 配置自定义域名
app.reconcileflow.com - Vercel 自动申请 SSL 证书,HTTPS 即刻生效
后端 API:保守策略
你的 API Routes 在 Next.js 里,天然和前端一起部署在 Vercel。对于现在的规模(几十个客户),全放 Vercel 完全够。不需要单独部署后端服务。
注意:Vercel 的 Serverless Function 有 10 秒超时限制。如果你的对账匹配需要处理超大文件(>30 秒),后续可能需要把匹配引擎拆到一个独立服务中。但现在不需要——现有客户没有超过 10 秒的匹配任务。
数据库:从 Supabase 免费层升级
免费层 500MB 即将不够。你升级到 Supabase Pro($25/月):
- 8GB 空间
- 自动备份(每天一次,保留 7 天)
- 连接池(PgBouncer)——防止「连接数打满」的事故再次发生
文件存储(新增)
客户之前上传的 CSV 文件存在本地磁盘。上云后,Vercel 的 Serverless 环境没有持久化磁盘。你需要对象存储:
- 方案:AWS S3 或 Cloudflare R2
- R2 优势:不收流量费(S3 出站流量要付费),适合小规模
// 文件上传改为直接上传到 R2
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const r2 = new S3Client({
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY!,
secretAccessKey: process.env.R2_SECRET_KEY!,
},
});
// 上传后拿到 URL,存到数据库
const url = `https://files.reconcileflow.com/${teamId}/${fileId}.csv`;
第三步:CI/CD 从零搭建
你受够了 ssh → git pull → npm run build → pm2 restart 的手动流程。现在是时候用 GitHub Actions 自动化了。
最小可行的 CI/CD 流水线
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run typecheck # TypeScript 类型检查
- run: npm run lint # ESLint 代码规范检查
- run: npm run test # 单元测试 + 集成测试
- run: npx prisma generate # 确保 Prisma Schema 没有语法错误
deploy-preview:
if: github.event_name == 'pull_request'
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
# Preview 部署,PR 评论里自动回链接
deploy-production:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
你的新部署流程:
- 改代码 →
git commit→git push - GitHub Actions 自动跑:TypeCheck → Lint → Test
- 如果全绿 → 自动部署到 Vercel Production
- 如果有 PR → 自动部署 Preview 环境,生成一个
xxx-preview.vercel.app链接
从手动 5 分钟变成全自动 3 分钟,而且你可以在手机上完成。 以后在外面吃饭,收到 bug 报告,手机改一行代码 → push → 自动部署。再也不怕离开电脑了。
Preview 环境的妙用
当 G 说「能不能试试审批功能」时,你不再需要说「等上线以后」。你在一个 feature 分支上开发,GitHub 自动在 PR 里生成一个 Preview 链接:
「G,你试试这个链接,审批功能在这里。数据是测试数据,随便操作。」
Preview 环境让客户验收变成了一个自动化的流程,而不是让你给他开一个新服务器。
第四步:数据库迁移自动化
以前你手动 prisma db push。现在你有了 CI/CD,数据库迁移也需要自动化。
# .github/workflows/deploy.yml 中添加
- name: Run database migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
关键原则:永远在生产部署之前跑 Migration。如果 Migration 失败,部署就此中止,旧的代码还在跑。如果部署先跑了但 Migration 没跑,新代码可能访问不存在的表→ 500。
迁移的黄金法则
- 永远不要手动改生产数据库。所有 Schema 变更都通过
prisma migrate dev生成 Migration 文件,提交到 Git - Migration 文件要 Code Review。加一个字段看起来无害,但如果加了
NOT NULL又没设默认值,Migration 就会把现有数据全毁掉 - 先跑备份,再跑 Migration。虽然 Supabase 有自动备份,但你在跑破坏性 Migration 之前手动备份一次,多一层保险
域名和 SSL
你之前一直用 http://12.34.56.78:3000 这种 IP 地址。现在需要一个专业的域名。
- 买域名:
reconcileflow.com(Namecheap/Cloudflare Registrar,$12/年) - DNS 指向 Vercel:在 Cloudflare 配 CNAME 记录
- Vercel 自动签发 SSL 证书
app.reconcileflow.com→ 产品主页api.reconcileflow.com→ API(如果需要独立出来)- 设置
www301 重定向到裸域
给客户发链接的时候终于不用 12.34.56.78 了。
上云后的成本
| 服务 | 用途 | 费用/月 |
|---|---|---|
| Vercel Pro | 前端 + API 部署 | $20 |
| Supabase Pro | 数据库 + 备份 | $25 |
| Cloudflare R2 | 文件存储 | ~$1(几百个 CSV) |
| GitHub Actions | CI/CD | 免费(公开仓库) |
| 域名 | reconcileflow.com | $1(折算月费) |
| 合计 | $47/月 |
年付按折扣算下来大约 250/月。基础设施成本占收入的 20% 左右,健康比例。
什么时候进入下一阶段?
你已经做到了:
- ✅ CI/CD 全自动:Push → 自动检查 → 自动部署
- ✅ 多环境:Local / Preview / Staging / Production
- ✅ 数据库自动备份 + Migration 自动化
- ✅ 域名 + SSL + CDN,专业级体验
- ✅ 基础设施月费 $47,占收入 20%
你的客户增长到了约 50 个,MRR 到了约 ¥10,000/月。业务稳定了。
但是新的信号出现了:
- 某个周一早上,数据库突然变慢——一个客户的对账查询跑了 30 秒,影响了其他所有客户
- 你第一次是从客户那里得知系统变慢了(而不是从监控面板),因为你还没有监控
- G 问你:「你们的 SLA 是多少?如果我们周一上午对账高峰期系统挂了,能保证多长时间恢复?」
进入下一阶段的信号:以下任一——
- 客户的生产环节依赖你的系统,你挂他们也挂
- 你第一次从客户那里得知系统出了故障(而不是自己先发现)
这是第九篇的内容——专业化。
本篇总结
| 做了什么 | 花了多久 |
|---|---|
| 多环境搭建(Local/Preview/Staging/Production) | 半天 |
| Vercel + Supabase 迁移 | 1 天 |
| Cloudflare R2 文件存储 | 2 小时 |
| GitHub Actions CI/CD | 半天 |
| 域名 + SSL | 1 小时 |
| 数据库 Migration 自动化 | 1 小时 |
当前系统形态: 有 CI/CD、有多环境管理、能自动部署的云服务。前端 Vercel + 数据库 Supabase + 文件 R2。50 个客户,不再需要手动 SSH。
**本月账单:20 + Supabase 1 + 域名 $1)。MRR ¥10,000/月。
如果是 ToC: 这个阶段基础设施成本会更低——你的前端和 API 放 Vercel Hobby(免费),数据库用 Supabase 免费层。等到 1000+ 用户再升级。ToC 的规模化靠用户量撑,基础设施成本增长比 ToB 更平滑。
下一步
客户依赖你的生产环境——进入第九篇:专业化,监控、日志、缓存、安全从「没有」到「可依赖」。