6月6日 20:23
Docker 容器怎么接入 CI/CD?GitHub Actions 和 GitLab CI 集成
代码提交后自动构建镜像、跑测试、部署——这就是 CI/CD 和 Docker 的结合。Docker 让构建环境一致、部署产物标准化,CI/CD 让这一切自动运行。这篇讲 Docker 在主流 CI/CD 平台中的集成方式。
Docker 在 CI/CD 中的三个角色
- 构建环境:CI Runner 本身跑在 Docker 容器里,保证每次构建环境一致
- 构建产物:
docker build产出镜像,推到 Registry,部署时直接拉镜像 - 部署目标:生产环境拉镜像启动容器,不需要在服务器上装运行时
GitHub Actions + Docker
基本构建和推送
yaml# .github/workflows/deploy.yml name: Build and Push Docker Image on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | myorg/myapp:latest myorg/myapp:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max
cache-from: type=gha 用 GitHub Actions 的缓存加速构建——没有变化的层直接复用,不用重新构建。首次构建可能要 5 分钟,后续只要 1-2 分钟。
多阶段构建减小镜像
dockerfile# 构建阶段 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 运行阶段——只有产物,没有源码和 devDependencies FROM node:20-alpine WORKDIR /app COPY /app/dist ./dist COPY /app/node_modules ./node_modules CMD ["node", "dist/index.js"]
多阶段构建让最终镜像只有运行时需要的文件——从 1GB+ 缩小到 200MB 以下。
GitLab CI + Docker
Docker-in-Docker 构建
yaml# .gitlab-ci.yml build: image: docker:24 services: - docker:24-dind variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
docker:24-dind 服务启动一个 Docker 守护进程,让 CI 容器里可以执行 docker build。注意要加 DOCKER_TLS_CERTDIR 确保通信安全。
使用 GitLab Container Registry
GitLab 自带 Container Registry,不用额外配 Docker Hub:
yamlscript: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
$CI_REGISTRY_IMAGE 是 GitLab 预设变量,指向当前项目的 Registry 地址。
镜像标签策略
bash# 不推荐:只用 latest myapp:latest # 推荐:语义化版本 + Git SHA 双标签 myapp:1.2.3 myapp:sha-abc1234 # 回滚时指定版本 docker pull myapp:1.2.2
| 标签 | 用途 | 示例 |
|---|---|---|
latest | 默认最新版 | 开发环境用 |
| 语义化版本 | 生产部署 | 1.2.3 |
| Git SHA | 精确追溯 | sha-abc1234 |
| 分支名 | 预览环境 | main, staging |
生产环境永远用精确版本号或 SHA,不用 latest——latest 指向的镜像随时可能变,出了问题无法回滚。
部署阶段
SSH 部署到服务器
yaml# GitHub Actions - name: Deploy to server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_HOST }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: | docker pull myorg/myapp:${{ github.sha }} docker stop myapp || true docker rm myapp || true docker run -d --name myapp \ --restart unless-stopped \ -p 3000:3000 \ myorg/myapp:${{ github.sha }}
docker-compose 部署
服务器上放一个 docker-compose.yml,CI 只需要触发 docker compose pull && docker compose up -d:
yaml# 服务器上的 docker-compose.yml services: app: image: myorg/myapp:latest ports: - "3000:3000" restart: unless-stopped
bash# CI 部署脚本 ssh deploy@server "cd /app && docker compose pull && docker compose up -d"
零停机部署
上面的方式会短暂中断服务。用双容器切换实现零停机:
bash# 1. 启动新容器在不同端口 docker run -d --name myapp-new -p 3001:3000 myorg/myapp:new-version # 2. 健康检查 until curl -f http://localhost:3001/health; do sleep 1; done # 3. 切换 Nginx 上游 # 更新 nginx upstream 指向 3001 docker exec nginx nginx -s reload # 4. 停掉旧容器 docker stop myapp-old && docker rm myapp-old # 5. 重命名新容器 docker rename myapp-new myapp-old
安全最佳实践
- 不要用 root 跑容器:Dockerfile 里加
USER app - 扫描镜像漏洞:
docker scout cves myapp:latest或 Trivy - 最小基础镜像:用
alpine或distroless代替ubuntu - Secret 不进镜像:数据库密码等通过环境变量或 Secret 注入,不写进 Dockerfile
.dockerignore排除无关文件:.git、node_modules、.env不应该进构建上下文
shell# .dockerignore .git node_modules .env *.md .dockerignore