
很多团队把 CI/CD 停留在「能跑通就行」的状态:没有测试 gate、没有制品管理、没有回滚机制,一出问题就只能手动救火。本文记录我用 GitHub Actions 搭建一条生产级 Node.js CI/CD 流水线的完整过程,踩过的坑、绕过的弯路,都真实记录。看完你直接复制配置就能用。
先说结论(TL;DR)
这条流水线实现了:代码 push → 自动执行单元测试 + E2E 测试 → 构建 Docker 镜像 → 推送私有镜像仓库 → 部署到服务器 → 自动回滚。核心原则是任何一个环节失败,后续自动中止,不让坏代码进入生产。
基础配置:触发条件和认证
workflow 文件放在 .github/workflows/ 目录下,文件名随意,GitHub 会自动读取。我命名为 pipeline.yml:
name: Production CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: registry.example.com
IMAGE_NAME: ${{ github.repository_owner }}/myapp
踩坑记录:最早期我只用 on: push 监听 main 分支,结果 develop 分支的测试完全没跑,合并到 main 才爆雷。正确的做法是 push 监听所有要保护的分支,pull_request 专门跑 PR 检查。
测试阶段:并行 + 缓存加速
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: myapp_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test -- --coverage
env:
DATABASE_URL: postgres://postgres:testpass@localhost:5432/myapp_test
NODE_ENV: test
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
第一个坑:最初没用 npm ci 用的是 npm install,在 Actions 缓存失效时每次都要重新下载巨量依赖包,一次 CI 跑 8 分钟。换用 npm ci + cache: 'npm' 后降到 2 分钟以内。
第二个坑:数据库 service 的 health check。我最初没加 --health-cmd pg_isready,PostgreSQL 容器启动需要几秒,前几个 step 已经开始跑了导致连不上数据库。加上 health check 后,Actions 会等数据库真正就绪再执行后续步骤。
构建阶段:Docker 多阶段构建
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASS }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=,suffix=,format=short
type=ref,event=branch
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
GIT_SHA=${{ github.sha }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
关键设计:
needs: test— 测试不过,构建不跑。if条件 — 只有 push 到 main/develop 才构建,PR 只跑测试不构建,节省资源。cache-from: type=gha— GitHub Actions 内置的缓存后端,比远程镜像仓库缓存快得多。Docker 层改动时只需要重建变化的层,全量构建从 5 分钟降到 40 秒。- 用 git SHA 作为镜像 tag,
latest只打在 main 分支上 — 方便回滚时精准定位。
部署阶段:SSH + 滚动更新
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
# 拉取最新镜像
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
# 滚动更新,不中断服务
docker compose up -d --no-deps --scale app=0
docker compose up -d --no-deps --scale app=2
# 健康检查
sleep 10
curl -f https://yourdomain.com/health || {
echo "Health check failed, rolling back..."
docker compose up -d --no-deps --scale app=2
exit 1
}
# 清理旧镜像(保留最近3个版本)
docker image prune -af --filter "until=72h" || true
- name: Notify deployment
if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-H 'Content-Type: application/json' \
-d '{"text":"🚨 部署失败,请检查!"}'
核心原则:健康检查失败自动回滚。这里用 --scale app=0 再 --scale app=2 实现滚动更新,但如果 health check 失败会执行回滚命令。真正上线后发现有 bug,git revert 后再 push 一次即可自动完成回滚。
完整流程图

完整流程:push 代码 → 跑测试(并行的单元测试 + E2E) → 构建 Docker 镜像 → 推送镜像仓库 → SSH 部署到生产 → 健康检查通过 → 通知成功。任一环节失败,后续自动中止。
安全注意事项
- 所有 secrets(密码、SSH key、registry 凭证)都用 GitHub Secrets,不要硬编码在 workflow 文件里。
- 生产服务器的 SSH key 权限要设为
600,否则 SSH action 会拒绝连接。 - Container registry 如果是私有的,确保
REGISTRY_USER对应的账号只有拉取和推送权限,不要给管理权限。
效果对比
| 指标 | 之前(手动部署) | 现在(Actions 自动化) |
|---|---|---|
| 部署频率 | 每周 1-2 次 | 随时提交随时部署 |
| 部署时间 | 30-60 分钟(含人工检查) | 约 5 分钟(自动) |
| 生产故障次数/月 | 3-5 次 | 0-1 次(回滚秒级) |
| 代码覆盖率 | 不知道(没跑) | >80%(强制 gate) |
最直接的感受是:以前周五晚上不敢合并代码,现在随时 push 都心里有底。测试覆盖率自动 gate,不达标不让构建;健康检查失败自动回滚,不让坏代码跑在生产上。
完整 workflow 文件已上传到我的 GitHub,有兴趣的可以直接 fork:https://github.com/yourname/actions-cicd-template
更多交流点击入群






