名称: 部署-cicd 描述: 使用CI/CD管道、容器化和基础设施即代码,自信地部署应用程序。涵盖GitHub Actions、Docker、Vercel和云部署模式。触发于部署、CI/CD、Docker、GitHub Actions或DevOps请求。 许可证: MIT
部署与CI/CD
可靠且自动地交付代码。
部署哲学
部署管道
代码 → 构建 → 测试 → 阶段 → 生产
│ │ │ │ │
└───────┴───────┴───────┴────────┘
自动化、可重复
原则
- 自动化一切 - 无手动步骤
- 快速失败 - 及早发现问题
- 准备回滚 - 总有逃生路径
- 环境一致性 - 开发 ≈ 阶段 ≈ 生产
- 可观察性 - 了解正在发生什么
GitHub Actions
基本工作流
# .github/workflows/ci.yml
名称: CI
触发于:
推送:
分支: [主]
拉取请求:
分支: [主]
任务:
构建和测试:
运行在: ubuntu最新版
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Node.js
使用: actions/setup-node@v4
带:
节点版本: '20'
缓存: 'npm'
- 名称: 安装依赖
运行: npm ci
- 名称: 运行检查器
运行: npm run lint
- 名称: 运行测试
运行: npm test
- 名称: 构建
运行: npm run build
矩阵构建
任务:
测试:
运行在: ubuntu最新版
策略:
矩阵:
节点版本: [18, 20, 22]
操作系统: [ubuntu最新版, macos最新版]
步骤:
- 使用: actions/checkout@v4
- 使用: actions/setup-node@v4
带:
节点版本: ${{ matrix.node-version }}
- 运行: npm ci
- 运行: npm test
推送到主分支时部署
名称: 部署
触发于:
推送:
分支: [主]
任务:
部署:
运行在: ubuntu最新版
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Node.js
使用: actions/setup-node@v4
带:
节点版本: '20'
缓存: 'npm'
- 运行: npm ci
- 运行: npm run build
- 名称: 部署到Vercel
使用: amondnet/vercel-action@v25
带:
vercel令牌: ${{ secrets.VERCEL_TOKEN }}
vercel组织ID: ${{ secrets.VERCEL_ORG_ID }}
vercel项目ID: ${{ secrets.VERCEL_PROJECT_ID }}
vercel参数: '--prod'
秘密管理
环境:
数据库URL: ${{ secrets.DATABASE_URL }}
API密钥: ${{ secrets.API_KEY }}
# 在GitHub中: 设置 → 秘密和变量 → Actions
缓存
- 名称: 缓存节点模块
使用: actions/cache@v3
带:
路径: ~/.npm
密钥: ${{ runner.os }}-节点-${{ hashFiles('**/package-lock.json') }}
恢复密钥: |
${{ runner.os }}-节点-
Docker
Node.js Dockerfile
# 构建阶段
FROM node:20-alpine AS 构建器
工作目录 /app
复制 package*.json ./
运行 npm ci
复制 . .
运行 npm run build
# 生产阶段
FROM node:20-alpine AS 运行器
工作目录 /app
环境变量 NODE_ENV=production
复制 --from=构建器 /app/package*.json ./
复制 --from=构建器 /app/node_modules ./node_modules
复制 --from=构建器 /app/.next ./.next
复制 --from=构建器 /app/public ./public
暴露 3000
命令 ["npm", "start"]
Docker Compose
# docker-compose.yml
版本: '3.8'
服务:
应用:
构建: .
端口:
- "3000:3000"
环境:
- 数据库URL=postgresql://postgres:password@db:5432/mydb
依赖于:
- 数据库
数据库:
镜像: postgres:15-alpine
环境:
后置用户: postgres
后置密码: password
后置数据库: mydb
卷:
- 后置数据:/var/lib/postgresql/data
端口:
- "5432:5432"
卷:
后置数据:
.dockerignore
node_modules
.git
.gitignore
README.md
.env
.env.*
Dockerfile
docker-compose.yml
.next
coverage
平台部署
Vercel (Next.js)
// vercel.json
{
"框架": "nextjs",
"构建命令": "npm run build",
"输出目录": ".next",
"环境": {
"数据库URL": "@database-url"
},
"头信息": [
{
"源": "/api/(.*)",
"头信息": [
{ "键": "缓存控制", "值": "不存储" }
]
}
]
}
Railway
# railway.toml
[构建]
构建器 = "nixpacks"
构建命令 = "npm run build"
[部署]
启动命令 = "npm start"
健康检查路径 = "/api/health"
健康检查超时 = 100
重启策略类型 = "失败时"
重启策略最大重试 = 3
Fly.io
# fly.toml
应用 = "我的应用"
主要区域 = "ord"
[构建]
dockerfile = "Dockerfile"
[http服务]
内部端口 = 3000
强制HTTPS = true
自动停止机器 = true
自动启动机器 = true
最小运行机器 = 0
[[服务]]
内部端口 = 3000
协议 = "tcp"
[[服务.端口]]
端口 = 80
处理器 = ["http"]
[[服务.端口]]
端口 = 443
处理器 = ["tls", "http"]
环境管理
环境文件
.env # 共享默认值(提交)
.env.local # 本地覆盖(git忽略)
.env.development # 开发特定
.env.production # 生产特定
.env.test # 测试特定
环境变量模式
// lib/env.ts
导入 { z } 从 'zod';
常量 环境模式 = z.对象({
数据库URL: z.字符串().url(),
API密钥: z.字符串().最小长度(1), <!-- allow-secret -->
节点环境: z.枚举(['development', 'production', 'test']),
});
导出 常量 环境 = 环境模式.解析(process.env);
数据库迁移
Prisma迁移
# 创建迁移
npx prisma migrate dev --name init
# 应用迁移(CI/CD)
npx prisma migrate deploy
# 生成客户端
npx prisma generate
CI/CD中的迁移
- 名称: 运行数据库迁移
运行: npx prisma migrate deploy
环境:
数据库URL: ${{ secrets.DATABASE_URL }}
回滚策略
蓝绿部署
流量 → │ 蓝 │ ← 当前
└─────────┘
┌─────────┐
│ 绿 │ ← 新版本(测试)
└─────────┘
验证后: 切换流量到绿
金丝雀部署
90% ────→ │ 当前 │
└─────────┘
┌─────────┐
10% ────→ │ 新 │ ← 监控问题
└─────────┘
逐步增加新版本流量
即时回滚(Vercel)
# 回滚到之前部署
vercel rollback
# 或通过面板: 部署 → ... → 提升到生产
健康检查
健康端点
// app/api/health/route.ts
导入 { 数据库 } 从 '@/lib/db';
导出 异步 函数 GET() {
尝试 {
// 检查数据库
等待 数据库.$queryRaw`SELECT 1`;
返回 Response.json({
状态: '健康',
时间戳: 新 Date().toISOString(),
版本: process.env.应用版本 || '未知',
});
} 捕获 (错误) {
返回 Response.json(
{ 状态: '不健康', 错误: '数据库连接失败' },
{ 状态: 503 }
);
}
}
完整CI/CD管道
名称: CI/CD
触发于:
推送:
分支: [主, 开发]
拉取请求:
分支: [主]
环境:
节点版本: '20'
任务:
检查:
运行在: ubuntu最新版
步骤:
- 使用: actions/checkout@v4
- 使用: actions/setup-node@v4
带:
节点版本: ${{ env.NODE_VERSION }}
缓存: 'npm'
- 运行: npm ci
- 运行: npm run lint
测试:
运行在: ubuntu最新版
步骤:
- 使用: actions/checkout@v4
- 使用: actions/setup-node@v4
带:
节点版本: ${{ env.NODE_VERSION }}
缓存: 'npm'
- 运行: npm ci
- 运行: npm test -- --coverage
- 使用: codecov/codecov-action@v3
构建:
需要: [检查, 测试]
运行在: ubuntu最新版
步骤:
- 使用: actions/checkout@v4
- 使用: actions/setup-node@v4
带:
节点版本: ${{ env.NODE_VERSION }}
缓存: 'npm'
- 运行: npm ci
- 运行: npm run build
- 使用: actions/upload-artifact@v3
带:
名称: 构建
路径: .next
部署预览:
如果: github.event_name == 'pull_request'
需要: 构建
运行在: ubuntu最新版
步骤:
- 使用: actions/checkout@v4
- 使用: amondnet/vercel-action@v25
带:
vercel令牌: ${{ secrets.VERCEL_TOKEN }}
vercel组织ID: ${{ secrets.VERCEL_ORG_ID }}
vercel项目ID: ${{ secrets.VERCEL_PROJECT_ID }}
部署生产:
如果: github.ref == 'refs/heads/main'
需要: 构建
运行在: ubuntu最新版
环境: 生产
步骤:
- 使用: actions/checkout@v4
- 使用: amondnet/vercel-action@v25
带:
vercel令牌: ${{ secrets.VERCEL_TOKEN }}
vercel组织ID: ${{ secrets.VERCEL_ORG_ID }}
vercel项目ID: ${{ secrets.VERCEL_PROJECT_ID }}
vercel参数: '--prod'
参考
references/github-actions-recipes.md- 常见工作流模式references/docker-patterns.md- Docker最佳实践references/monitoring-setup.md- 可观察性配置