name: deployment-cicd description: 使用CI/CD管道、容器化和基础设施即代码自信地部署应用。涵盖GitHub Actions、Docker、Vercel和云部署模式。在部署、CI/CD、Docker、GitHub Actions或DevOps请求时触发。 license: MIT
部署与持续集成/持续交付
可靠且自动地发布代码。
部署哲学
部署管道
代码 → 构建 → 测试 → 暂存 → 生产
│ │ │ │ │
└───────┴───────┴───────┴────────┘
自动化、可重复
原则
- 自动化一切 - 无手动步骤
- 快速失败 - 尽早发现问题
- 回滚就绪 - 始终有逃生方案
- 环境一致性 - 开发 ≈ 暂存 ≈ 生产
- 可观测性 - 了解正在发生的事情
GitHub Actions
基础工作流
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 运行代码检查
run: npm run lint
- name: 运行测试
run: npm test
- name: 构建
run: npm run build
矩阵构建
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
推送至主分支时部署
name: 部署
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: 部署到 Vercel
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'
密钥管理
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
# 在GitHub中:设置 → 密钥和变量 → Actions
缓存
- name: 缓存节点模块
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Docker
Node.js Dockerfile
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["npm", "start"]
Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
depends_on:
- db
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
.dockerignore
node_modules
.git
.gitignore
README.md
.env
.env.*
Dockerfile
docker-compose.yml
.next
coverage
平台部署
Vercel (Next.js)
// vercel.json
{
"framework": "nextjs",
"buildCommand": "npm run build",
"outputDirectory": ".next",
"env": {
"DATABASE_URL": "@database-url"
},
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-store" }
]
}
]
}
Railway
# railway.toml
[build]
builder = "nixpacks"
buildCommand = "npm run build"
[deploy]
startCommand = "npm start"
healthcheckPath = "/api/health"
healthcheckTimeout = 100
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 3
Fly.io
# fly.toml
app = "my-app"
primary_region = "ord"
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[[services]]
internal_port = 3000
protocol = "tcp"
[[services.ports]]
port = 80
handlers = ["http"]
[[services.ports]]
port = 443
handlers = ["tls", "http"]
环境管理
环境文件
.env # 共享默认值(提交到版本控制)
.env.local # 本地覆盖(git忽略)
.env.development # 开发特定
.env.production # 生产特定
.env.test # 测试特定
环境变量模式
// lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1), <!-- 允许密钥 -->
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = envSchema.parse(process.env);
数据库迁移
Prisma 迁移
# 创建迁移
npx prisma migrate dev --name init
# 应用迁移(CI/CD)
npx prisma migrate deploy
# 生成客户端
npx prisma generate
CI/CD 中的迁移
- name: 运行数据库迁移
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
回滚策略
蓝绿部署
┌─────────┐
流量 → │ 蓝版 │ ← 当前版本
└─────────┘
┌─────────┐
│ 绿版 │ ← 新版本(测试中)
└─────────┘
验证后:切换流量到绿版
金丝雀部署
┌─────────┐
90% ────→ │ 当前版本 │
└─────────┘
┌─────────┐
10% ────→ │ 新版本 │ ← 监控问题
└─────────┘
逐渐增加新版本流量
即时回滚(Vercel)
# 回滚到前一个部署
vercel rollback
# 或通过仪表板:部署 → ... → 提升到生产
健康检查
健康端点
// app/api/health/route.ts
import { db } from '@/lib/db';
export async function GET() {
try {
// 检查数据库
await db.$queryRaw`SELECT 1`;
return Response.json({
status: '健康',
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || '未知',
});
} catch (error) {
return Response.json(
{ status: '不健康', error: '数据库连接失败' },
{ status: 503 }
);
}
}
完整CI/CD管道
name: CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v3
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v3
with:
name: build
path: .next
deploy-preview:
if: github.event_name == 'pull_request'
needs: build
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 }}
deploy-production:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment: production
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'
参考
references/github-actions-recipes.md- 常见工作流模式references/docker-patterns.md- Docker最佳实践references/monitoring-setup.md- 可观测性配置