关于 deployment
deployment 是指导应用上线的技能,面向部署 Next.js、全栈、容器化和无服务器应用的开发者。内容涵盖平台选择、CI/CD、健康检查、监控与回滚,并附有部署检查脚本。
主要功能
- 01选择部署平台并配置环境
- 02设置 GitHub Actions CI/CD
- 03构建生产 Docker 镜像
- 04配置健康检查与监控
- 05规划预览部署和回滚
- 06运行部署前检查脚本
适用场景
- 01将 Next.js 应用部署到生产环境
- 02为容器服务设置上线流程
- 03为拉取请求配置预览部署
- 04检查上线配置并准备回滚
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mgd34msu/ goodvibes-plugin --skill deployment安装后在 Claude Code 中输入/deployment确认已加载。
附带文件references/deployment-platforms.mdscripts/validate-deployment.sh
详细介绍
资源
scripts/
validate-deployment.sh
references/
deployment-platforms.md
部署
本技能将指导你使用现代平台和工具将应用部署到生产环境。在部署 Next.js、全栈应用、容器化服务或无服务器函数时,请使用此工作流。
何时使用此技能
- 将应用部署到 Vercel、Railway、Fly.io 或 AWS
- 使用 GitHub Actions 设置 CI/CD 流水线
- 配置用于生产环境的 Docker 容器
- 实施运行状况检查和监控
- 为拉取请求创建预览部署
- 设置回滚和金丝雀部署策略
平台选择
根据应用需求选择合适的平台:
Vercel(最适合 Next.js)
适用场景:
- Next.js 应用(App Router 或 Pages Router)
- 带边缘函数的静态站点
- 为每个 PR 自动创建预览部署
- 零配置部署
设置:
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel --prod
环境变量:
# Set production secrets
vercel env add DATABASE_URL production
vercel env add NEXTAUTH_SECRET production
Railway(最适合全栈应用)
适用场景:
- 带数据库的全栈应用
- 单体仓库部署
- 托管 PostgreSQL、Redis、MongoDB
- 支持 WebSocket
设置:
# Install Railway CLI
npm install -g @railway/cli
# Login and deploy
railway login
railway up
railway.json:
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS",
"buildCommand": "npm run build"
},
"deploy": {
"startCommand": "npm start",
"healthcheckPath": "/api/health",
"healthcheckTimeout": 100,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}
Fly.io(最适合容器)
适用场景:
- 自定义容器需求
- 全球边缘部署
- 长时间运行的进程
- 精细控制扩缩容
设置:
# Install Fly CLI
curl -L https://fly.io/install.sh | sh
# Initialize and deploy
fly launch
fly deploy
fly.toml:
app = "my-app"
primary_region = "sjc"
[build]
dockerfile = "Dockerfile"
[env]
PORT = "8080"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[[http_service.checks]]
grace_period = "10s"
interval = "30s"
method = "GET"
timeout = "5s"
path = "/api/health"
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 256
Docker(最适合自托管)
适用场景:
- 自托管基础设施
- VPS 部署(DigitalOcean、Linode)
- 与本地开发环境保持一致
- 多服务编排
Dockerfile 示例请参见 references/deployment-platforms.md。
AWS(最适合企业)
适用场景:
- 企业需求
- 合规/监管要求
- 复杂基础设施
- 多区域部署
服务:
- ECS Fargate:无服务器容器
- Lambda:无服务器函数
- Amplify:全栈部署
- Elastic Beanstalk:托管平台
环境配置
.env 管理
切勿将密钥提交到 git。始终使用 .env.example 编写文档:
.env.example:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
# Authentication
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32"
# External APIs
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# Feature Flags
NEXT_PUBLIC_ENABLE_ANALYTICS="false"
环境变量验证
在构建时验证环境变量,以便尽早失败:
src/env.mjs:
import { z } from 'zod';
const server = z.object({
DATABASE_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
});
const client = z.object({
NEXT_PUBLIC_APP_URL: z.string().url(),
});
const processEnv = {
DATABASE_URL: process.env.DATABASE_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
};
const merged = server.merge(client);
const parsed = merged.safeParse(processEnv);
if (!parsed.success) {
console.error('[FAIL] Invalid environment variables:', parsed.error.flatten().fieldErrors);
throw new Error('Invalid environment variables');
}
export const env = parsed.data;
在应用顶部导入,以便在启动时进行验证:
import { env } from './env.mjs';
// Use typed, validated env
const db = new PrismaClient({
datasources: { db: { url: env.DATABASE_URL } },
});
CI/CD 流水线
GitHub Actions 工作流
创建 .github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [main]
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'
- name: Install dependencies
run: npm ci
- name: Lint
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'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
SKIP_ENV_VALIDATION: true
deploy:
runs-on: ubuntu-latest
needs: [build]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to 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'
缓存策略
使用适当的缓存加快 CI/CD:
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
${{ runner.os }}-nextjs-
Docker 生产环境设置
多阶段 Dockerfile
创建经过优化的生产环境 Dockerfile:
Dockerfile(Next.js):
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
next.config.js:
module.exports = {
output: 'standalone', // Required for Docker
};
.dockerignore
从 Docker 构建中排除不必要的文件:
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
.gitignore
.env*.local
.vscode
.idea
dist
build
coverage
*.md
!README.md
构建和运行
# Build the image
docker build -t my-app .
# Run with environment variables
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-e NEXTAUTH_SECRET="..." \
my-app
运行状况检查
运行状况检查端点
创建运行状况检查端点以进行监控:
app/api/health/route.ts:
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET() {
try {
// Check database connection
await prisma.$queryRaw`SELECT 1`;
return NextResponse.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
database: 'connected',
});
} catch (error) {
return NextResponse.json(
{
status: 'unhealthy',
timestamp: new Date().toISOString(),
database: 'disconnected',
error: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 503 }
);
}
}
Docker 运行状况检查
在 Dockerfile 中添加运行状况检查:
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
Kubernetes 存活/就绪探针
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
预览部署
自动 PR 预览
Vercel 和 Railway 会自动为拉取请求创建预览部署。
用于 Railway 的 GitHub Actions:
on:
pull_request:
branches: [main]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Railway (PR)
uses: bervProject/railway-deploy@main
with:
railway_token: ${{ secrets.RAILWAY_TOKEN }}
service: ${{ secrets.RAILWAY_SERVICE }}
在 PR 评论中添加预览 URL
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '[DEPLOY] Preview deployed to: https://pr-${{ github.event.number }}.myapp.com'
})
回滚策略
即时回滚(Vercel)
# List deployments
vercel ls
# Promote a previous deployment to production
vercel promote <deployment-url>
蓝绿部署
部署新版本,同时保留旧版本,然后切换流量:
# Deploy new version (green)
fly deploy --strategy bluegreen
# Traffic switches automatically after health checks pass
# Rollback if needed:
fly releases rollback
金丝雀部署
逐步将流量切换到新版本:
Fly.io 金丝雀部署:
# Deploy canary (10% traffic)
fly deploy --strategy canary
# Promote to 100% if successful
fly releases promote
监控和错误跟踪
Sentry 集成
npm install @sentry/nextjs
sentry.client.config.ts:
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
environment: process.env.NODE_ENV,
enabled: process.env.NODE_ENV === 'production',
});
sentry.server.config.ts:
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
environment: process.env.NODE_ENV,
});
正常运行时间监控
使用外部服务监控可用性:
- Better Uptime:https://betteruptime.com
- Pingdom:https://www.pingdom.com
- UptimeRobot:https://uptimerobot.com
每隔 1-5 分钟监控一次 /api/health 端点。
日志聚合
Vercel:
- 内置日志流式传输
- 与 Datadog、LogDNA、Axiom 集成
Railway:
- 仪表板内置日志
- 导出到外部服务
自托管:
# Use Docker logging driver
docker run --log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
my-app
CI/CD 中的数据库迁移
Prisma 迁移
在部署前运行迁移:
GitHub Actions:
- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Railway: 添加到 railway.json:
{
"deploy": {
"startCommand": "npx prisma migrate deploy && npm start"
}
}
迁移安全性
切勿自动运行有破坏性的迁移:
-
优先执行向后兼容的迁移
- 将新列添加为可空
- 部署兼容新旧架构的代码
- 运行迁移
- 部署依赖新架构的代码
- 在未来的迁移中删除旧列
-
生产环境需要手动审批
- name: Run migrations if: github.event_name == 'workflow_dispatch' run: npx prisma migrate deploy
Precision 工具集成
使用 precision_exec 验证部署
使用 precision_exec 运行部署命令并验证预期结果:
precision_exec:
commands:
- cmd: "npm run build"
expect:
exit_code: 0
- cmd: "docker build -t my-app ."
expect:
exit_code: 0
- cmd: "npm run typecheck"
expect:
exit_code: 0
verbosity: minimal
使用 precision_fetch 进行健康检查
验证部署健康状况:
precision_fetch:
requests:
- url: "https://my-app.com/api/health"
method: GET
expect:
status: 200
body_contains: '"status":"healthy"'
发现部署缺口
部署前,检查是否缺少配置:
discover:
queries:
- id: env_example
type: glob
patterns: [".env.example"]
- id: dockerfile
type: glob
patterns: ["Dockerfile", "docker-compose.yml"]
- id: ci_config
type: glob
patterns: [".github/workflows/*.yml"]
- id: health_check
type: grep
pattern: '/api/health|/health'
glob: "**/*.{ts,tsx,js,jsx}"
output_mode: count_only
部署前检查清单
运行验证脚本:
./plugins/goodvibes/skills/outcome/deployment/scripts/validate-deployment.sh /path/to/project
脚本会检查:
- Dockerfile 是否存在
- .env.example 是否存在且记录了必需变量
- CI/CD 配置是否存在
- 是否实现了健康检查端点
- .dockerignore 是否存在
- 代码中是否没有硬编码的密钥
- 构建命令是否成功
- 数据库迁移配置是否存在
常见陷阱
1. 缺少环境变量
问题: 未设置环境变量导致部署失败。
解决方案: 在 .env.example 中记录所有变量,并在构建时使用 zod 进行验证。
2. 数据库连接池
问题: 无服务器函数耗尽数据库连接。
解决方案: 使用连接池(PgBouncer、Prisma Accelerate、Supabase pooler)。
// Use connection pooler in serverless
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL, // Use pooled connection string
},
},
});
3. 构建输出未优化
问题: Docker 镜像过大,冷启动缓慢。
解决方案: 使用多阶段构建、Next.js 的 standalone 输出以及适当的 .dockerignore。
4. 每次部署都会运行迁移
问题: Prisma 迁移在每次容器启动时都会运行。
解决方案: 在 CI/CD 中将迁移步骤与应用启动分开。
5. 没有回滚计划
问题: 有问题的部署会破坏生产环境,且没有简单的修复方法。
解决方案: 使用支持即时回滚的平台(Vercel、Railway、Fly.io),或保留之前的 Docker 镜像。
总结
核心原则:
- 在构建时验证环境变量 - 尽早失败,而不是在生产环境中失败
- 自动化所有流程 - CI/CD 应负责 lint、测试、构建和部署
- 健康检查是必需的 - 每项服务都需要健康检查端点
- 为每个 PR 创建预览部署 - 在合并前发现问题
- 始终制定回滚计划 - 即时回滚优于向前修复
- 从第一天起就开始监控 - 错误跟踪和运行时间监控不可或缺
- 迁移存在风险 - 谨慎执行,并确保向后兼容
后续步骤:
- 在项目上运行
validate-deployment.sh - 使用 GitHub Actions 设置 CI/CD 流水线
- 在你的平台中配置环境变量
- 添加健康检查端点
- 在预发布环境中测试部署
- 部署到生产环境
- 设置监控和告警
有关详细的平台配置和模板,请参阅 references/deployment-platforms.md。