14. CI/CD 流水线中集成 Docker:GitHub Actions 自动构建部署

14. CI/CD 流水线中集成 Docker:GitHub Actions 自动构建部署

每次改完代码,你都要经历这套"手动三连":本地 docker builddocker push → SSH 到服务器 docker pull + docker compose up -d。改一行代码,操作五分钟。要是哪天忘了推镜像、忘了重启容器,线上跑的还是老版本,排查半天才发现"哦,我没部署"。这种日子,该结束了。CI/CD 就是来帮你把这些重复操作全自动化的------代码一推,镜像自动构建、自动推送、自动部署,你只管写代码就行。

▲ 把"手动三连"交给流水线:代码一推,构建、测试、部署一条龙自动跑完。


一、什么是 CI/CD?Docker 在里面扮演什么角色?

1.1 一分钟搞懂 CI/CD

CI(持续集成)CD(持续部署/交付) 听起来高大上,但核心思想很朴素:

概念 全称 一句话解释
CI Continuous Integration 代码一推送到仓库,自动跑测试、自动构建产物
CD Continuous Deployment 构建完自动部署到服务器,中间不用人插手

串起来就是:代码提交 → 自动测试 → 自动构建镜像 → 自动推送 → 自动部署。整条链路无人值守,跑完线上就是最新的。

1.2 Docker 在 CI/CD 中的角色

Docker 是这条流水线里的"标准化打包环节":

复制代码
代码提交 → [自动测试] → [Docker 构建镜像] → [推送镜像到仓库] → [服务器拉取并部署]
                           ↑                    ↑                  ↑
                      你的 Dockerfile      Docker Hub / ACR     docker compose up

▲ 每一步都由流水线自动接力,你只需要把代码推上去。

为什么用 Docker 做 CI/CD 特别合适? 因为镜像天然就是"不可变产物"------同一个 Dockerfile + 同一份代码,在任何机器上构建出来的镜像都一模一样。这保证了"开发能跑,生产也能跑"。

1.3 常见的 CI/CD 工具

工具 特点 适合场景
GitHub Actions 免费额度大、跟 GitHub 深度集成、YAML 配置 开源项目、个人项目、中小团队
GitLab CI 内置于 GitLab、Pipeline 概念强大 用 GitLab 的团队
Jenkins 老牌王者、插件生态丰富 大型企业、复杂流水线
阿里云效 / 腾讯云 CODING 国内云厂商方案、中文友好 国内团队、需要国内网络加速

本文以 GitHub Actions 为例,因为它免费、配置简单、跟 GitHub 仓库无缝集成,上手最快。文章末尾也会简要介绍 Jenkins 和 GitLab CI 的等效方案。


二、GitHub Actions 基础概念

在动手写配置之前,先搞清楚几个核心概念:

概念 解释 类比
Workflow 一个 .yml 文件就是一个工作流,定义在 .github/workflows/ 目录下 一份"施工图纸"
Event(事件) 触发工作流的条件,比如 push、pull_request、打 tag "开工信号"
Job(作业) 工作流里的一组步骤,一个工作流可以有多个 Job "施工阶段"
Step(步骤) Job 里的单个操作,可以是运行命令或调用现成的 Action "具体动作"
Action 社区共享的可复用步骤(类似于 Docker Hub 上的镜像) "预制件"
Runner 执行 Job 的虚拟机(GitHub 免费提供 Linux/Mac/Windows) "施工队"

用一个流程图理解它们的关系:

复制代码
Event(push 到 main)
    │
    ▼
Workflow(docker-build.yml)
    │
    ├── Job 1: build-and-push
    │     ├── Step 1: checkout 代码
    │     ├── Step 2: 登录 Docker Hub
    │     ├── Step 3: 构建镜像
    │     └── Step 4: 推送镜像
    │
    └── Job 2: deploy(依赖 Job 1 完成)
          ├── Step 1: SSH 到服务器
          └── Step 2: docker compose up -d

三、实战:编写完整的 GitHub Actions 工作流

3.1 项目结构

假设你有一个简单的 Node.js 项目(换成 Python、Java 都一样,改 Dockerfile 就行):

复制代码
my-app/
├── .github/
│   └── workflows/
│       └── docker-build.yml     ← 工作流文件
├── src/
│   └── index.js
├── docker-compose.yml
├── Dockerfile
├── package.json
└── .dockerignore

3.2 Dockerfile(构建目标)

复制代码
FROM node:20-alpine
​
WORKDIR /app
​
# 先复制依赖文件,利用 Docker 缓存层
COPY package.json package-lock.json ./
RUN npm ci --production
​
# 再复制源码
COPY src/ ./src/
​
EXPOSE 3000
​
CMD ["node", "src/index.js"]

3.3 完整的工作流文件

这是本文的核心产出------一个可以直接拿去用的 GitHub Actions 配置:

复制代码
# .github/workflows/docker-build.yml
​
name: Docker Build and Deploy
​
on:
  # 推送到 main 分支时触发
  push:
    branches: [main]
    # 打 tag 时也触发(用于发布正式版本)
    tags: ['v*']
  # 允许手动触发
  workflow_dispatch:
​
# 定义环境变量
env:
  DOCKER_IMAGE: your-dockerhub-username/my-app
  # GitHub 自带的容器镜像仓库(可选方案)
  GHCR_IMAGE: ghcr.io/${{ github.repository }}
​
jobs:
  # ========== Job 1:构建并推送镜像 ==========
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      # Step 1: 拉取代码
      - name: Checkout code
        uses: actions/checkout@v4
​
      # Step 2: 设置 Docker Buildx(支持高级构建特性)
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
​
      # Step 3: 登录 Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
​
      # Step 4: 登录 GitHub Container Registry(可选,双推)
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
​
      # Step 5: 生成镜像标签
      - name: Generate image tags
        id: meta
        run: |
          # 获取 Git 短哈希(用于唯一标识每次构建)
          SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
          echo "sha_tag=${SHORT_SHA}" >> $GITHUB_OUTPUT
​
          # 根据触发方式生成不同的标签
          if [[ "${{ github.ref_type }}" == "tag" ]]; then
            # 打 tag 触发:用版本号 + latest
            VERSION=${GITHUB_REF_NAME#v}
            echo "tags=${{ env.DOCKER_IMAGE }}:${VERSION},${{ env.DOCKER_IMAGE }}:latest" >> $GITHUB_OUTPUT
            echo "env_name=production" >> $GITHUB_OUTPUT
          elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
            # push 到 main:用 dev + sha
            echo "tags=${{ env.DOCKER_IMAGE }}:dev,${{ env.DOCKER_IMAGE }}:${SHORT_SHA}" >> $GITHUB_OUTPUT
            echo "env_name=dev" >> $GITHUB_OUTPUT
          fi
​
      # Step 6: 构建并推送镜像(带缓存优化)
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: |
            org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
            org.opencontainers.image.revision=${{ github.sha }}
          # 缓存优化:利用 GitHub Actions 缓存
          cache-from: type=gha
          cache-to: type=gha,mode=max
​
  # ========== Job 2:部署到服务器 ==========
  deploy:
    needs: build-and-push   # 必须等 Job 1 完成
    runs-on: ubuntu-latest
    # 仅在推送到 main 或打 tag 时部署
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
    steps:
      - name: Deploy to server via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: 22
          script: |
            cd /opt/my-app
​
            # 拉取最新镜像
            docker compose pull
​
            # 重新创建容器(只替换有变化的服务)
            docker compose up -d --remove-orphans
​
            # 清理悬空镜像,释放磁盘空间
            docker image prune -f
​
            # 打印当前运行的容器状态
            docker compose ps
​
            echo "✅ 部署完成!镜像版本:${{ needs.build-and-push.outputs.image_tag }}"

3.4 镜像标签策略详解

上面用了一段 Shell 脚本来生成标签,这里用表格总结一下策略:

触发条件 生成的标签 用途
push 到 main 分支 dev + a1b2c3d(Git 短哈希) 开发环境自动部署
打 tag v1.2.3 1.2.3 + latest 生产环境正式发布
手动触发 同 push 到 main 临时构建测试

为什么要同时打两个标签?

  • dev / 1.2.3:可追踪的固定版本,出问题可以回滚

  • a1b2c3d(Git SHA):精确到"哪次提交构建的",排查问题利器

  • latest:方便本地开发时 docker pull 拿最新版


四、配置 GitHub Secrets(关键步骤)

工作流里用到了好几个敏感信息,绝对不能硬编码在 YAML 文件里(推到公开仓库就泄露了)。需要用 GitHub Secrets:

4.1 需要配置的 Secrets

Secret 名称 获取方式
DOCKERHUB_USERNAME 你的 Docker Hub 用户名 注册 Docker Hub 时的用户名
DOCKERHUB_TOKEN Docker Hub 访问令牌 Docker Hub → Account Settings → Security → New Access Token
SERVER_HOST 服务器 IP 或域名 你的云服务器公网地址
SERVER_USER SSH 用户名 通常是 rootubuntu
SERVER_SSH_KEY SSH 私钥内容 cat ~/.ssh/id_rsa(服务器配好公钥后)

4.2 配置步骤

  1. 打开你的 GitHub 仓库页面

  2. 进入 Settings → Secrets and variables → Actions

  3. 点击 New repository secret

  4. 逐个填入上面的 Secret 名称和值

重要提醒 :Docker Hub 的 Token 权限选择 Read/Write ,否则推送镜像时会报 denied: requested access to the resource is denied

4.3 服务器端准备

在你的云服务器上,确保以下环境已就绪:

复制代码
# 1. 安装 Docker 和 Docker Compose(参考本系列第 2 篇)
docker --version
docker compose version
​
# 2. 登录 Docker Hub(让服务器有拉取私有镜像的权限)
docker login
​
# 3. 创建应用目录,放好 docker-compose.yml
mkdir -p /opt/my-app
cd /opt/my-app
​
# 4. 创建 docker-compose.yml(服务器版本)
cat > docker-compose.yml << 'EOF'
version: "3.8"
​
services:
  app:
    image: your-dockerhub-username/my-app:dev
    container_name: my-app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=${DB_HOST}
    restart: always
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
EOF

五、缓存优化:让构建速度翻倍

5.1 为什么需要缓存?

每次 CI 构建都是在一台全新的虚拟机上跑,没有之前的构建层缓存。一个 Node.js 项目光 npm install 就要 1-2 分钟,Java Maven 项目更夸张------首次构建可能 5-10 分钟。

5.2 GitHub Actions 缓存方案

上面的工作流已经用上了:

复制代码
cache-from: type=gha
cache-to: type=gha,mode=max

这两行的含义:

参数 说明
type=gha 使用 GitHub Actions 内置缓存(无需额外配置)
mode=max 缓存所有层(不只是最终层),下次构建跳过未变化的层

实测效果

场景 无缓存 有缓存 提速
仅修改源码(依赖不变) 2 分 30 秒 45 秒 3.3 倍
修改了 package.json 2 分 30 秒 1 分 50 秒 1.4 倍
首次构建 / 缓存失效 2 分 30 秒 2 分 30 秒 无提速

进阶 :如果你用 GitHub Container Registry,还可以用 registry 类型缓存,把缓存层直接推到镜像仓库里:

复制代码
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max

六、多环境部署策略

真实项目通常有 dev / staging / prod 三套环境。下面展示如何用一套工作流覆盖多环境:

复制代码
# 在工作流顶部添加环境判断
jobs:
  build-and-push:
    # ...(同上面的构建 Job)

  deploy-dev:
    needs: build-and-push
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to DEV
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEV_SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/my-app
            # dev 环境用 dev 标签
            sed -i 's|image:.*|image: your-dockerhub-username/my-app:dev|' docker-compose.yml
            docker compose pull && docker compose up -d

  deploy-prod:
    needs: build-and-push
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    environment: production    # ← GitHub 环境保护:可以配置审批人
    steps:
      - name: Deploy to PRODUCTION
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/my-app
            VERSION=${GITHUB_REF_NAME#v}
            sed -i "s|image:.*|image: your-dockerhub-username/my-app:${VERSION}|" docker-compose.yml
            docker compose pull && docker compose up -d

关键设计

  • deploy-dev:push 到 main 自动部署,用 dev 标签

  • deploy-prod:只有打 tag(如 v1.0.0)才触发,用版本号标签

  • environment: production:可以在 GitHub 上配置审批人,防止误操作


七、工作流运行效果

配置完成后,每次推代码到 main 分支,GitHub Actions 会自动触发。在仓库的 Actions 标签页可以看到运行记录:

复制代码
✅ Docker Build and Deploy    #87    pushed 2 minutes ago
├── ✅ build-and-push          completed in 1m 23s
│   ├── ✅ Checkout code                     5s
│   ├── ✅ Set up Docker Buildx              8s
│   ├── ✅ Login to Docker Hub               2s
│   ├── ✅ Login to GHCR                     2s
│   ├── ✅ Generate image tags               1s
│   └── ✅ Build and push                   1m 05s
│
└── ✅ deploy                  completed in 32s
    └── ✅ Deploy to server via SSH         30s

推送镜像到 Docker Hub 后的效果

复制代码
your-dockerhub-username/my-app
├── latest      ← 最新正式版(打 tag 时更新)
├── 1.2.3       ← 版本号标签
├── dev         ← 开发版(push 到 main 时更新)
├── a1b2c3d     ← Git 短哈希标签(精确到提交)
└── buildcache  ← 构建缓存(不用于运行,仅加速构建)

八、其他 CI/CD 工具等效方案

如果你不用 GitHub Actions,下面是 Jenkins 和 GitLab CI 的等效配置概要。

8.1 GitLab CI

GitLab CI 的配置文件是项目根目录的 .gitlab-ci.yml

复制代码
# .gitlab-ci.yml

stages:
  - build
  - deploy

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind        # Docker in Docker
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  only:
    - main

deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
  script:
    - ssh -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_HOST "cd /opt/my-app && docker compose pull && docker compose up -d"
  only:
    - main
  needs: ["build"]

8.2 Jenkins Pipeline

Jenkins 用 Jenkinsfile 定义流水线:

复制代码
// Jenkinsfile

pipeline {
    agent any

    environment {
        DOCKER_IMAGE = 'your-dockerhub-username/my-app'
        DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')
    }

    stages {
        stage('Build') {
            steps {
                sh "docker build -t ${DOCKER_IMAGE}:${env.BUILD_NUMBER} ."
            }
        }
        stage('Push') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'dockerhub-credentials',
                    usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
                    sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin"
                    sh "docker push ${DOCKER_IMAGE}:${env.BUILD_NUMBER}"
                }
            }
        }
        stage('Deploy') {
            steps {
                sshagent(['server-ssh-key']) {
                    sh """
                        ssh -o StrictHostKeyChecking=no root@your-server \
                            'cd /opt/my-app && docker compose pull && docker compose up -d'
                    """
                }
            }
        }
    }
}

三者对比

特性 GitHub Actions GitLab CI Jenkins
配置文件 .github/workflows/*.yml .gitlab-ci.yml Jenkinsfile
运行环境 GitHub 提供 Runner GitLab 提供 / 自建 自建服务器
免费额度 公开仓库无限,私有仓库 2000 分钟/月 公开仓库无限,私有仓库 400 分钟/月 完全自建,无限制
生态插件 GitHub Marketplace(几千个 Action) GitLab 内置 + 社区 插件生态最丰富(1800+)
上手难度 ⭐ 简单 ⭐⭐ 中等 ⭐⭐⭐ 较复杂

九、常见问题与避坑

问题 原因 解决方案
denied: requested access to the resource is denied Docker Hub Token 权限不足 重新生成 Token,权限选 Read/Write
buildx failed with: executor failed running Dockerfile 里有命令执行失败 查看构建日志,通常是 npm installapt-get 报错
SSH 连接超时 服务器安全组未开放 22 端口 云服务器控制台放行 22 端口
docker compose pull 拉取超时 服务器网络到 Docker Hub 慢 配置镜像加速(参考本系列第 2 篇)
磁盘空间不足 旧镜像堆积 定期运行 docker image prune -a 或用 autoprune
Actions 不触发 YAML 文件路径或格式错误 确认文件在 .github/workflows/ 目录下,YAML 语法正确

十、本文要点回顾

要点 总结
CI/CD 是什么 代码提交后自动构建、测试、部署的全流程自动化
GitHub Actions 核心概念 Workflow → Event → Job → Step → Action
工作流文件 .github/workflows/docker-build.yml,定义了从构建到部署的完整流程
镜像标签策略 dev 环境用 dev + Git SHA,生产环境用版本号 + latest
缓存优化 cache-from: type=gha 利用 GitHub 缓存,构建速度提升 3 倍+
敏感信息 全部放 GitHub Secrets,永远不要硬编码在代码里
多环境部署 if 条件 + environment 保护,区分 dev / prod
等效工具 GitLab CI(.gitlab-ci.yml)、Jenkins(Jenkinsfile),原理相同

CI/CD 的本质是"把人的操作变成机器的流程"。 Docker 在其中扮演"标准化打包"的角色------同一份 Dockerfile 构建出的镜像,在任何环境都一模一样,这就是 CI/CD 能可靠运行的基石。


下集预告

到这里,Docker 的日常使用和自动化部署都讲完了。下一篇我们进入底层原理篇------Docker 到底是怎么做到"隔离"的?Namespace、Cgroup、UnionFS 这三大核心技术,将揭开容器"看起来像虚拟机,其实不是"的秘密。理解这些原理,你在排查容器问题时就不再是"黑盒调试"了。


参考资料

相关推荐
hulihutu441 小时前
拒绝延期、bug、货不对板:2026 管理系统定制开发落地保障手册
github·bug·鸿蒙系统·数据库管理员
隔窗听雨眠1 小时前
活动中台系统慢SQL治理实践:从监控告警到性能优化的全链路方法论
sql·性能优化·github
10mAh1 小时前
【Docker】磁盘空间被占满怎么清理?——overlay2、容器日志与 Build Cache 排查实战
docker·容器·eureka
xhaxy1 小时前
docker,k8s安装,k8s集群搭建(OS7)
docker·容器·kubernetes
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(60):MemInsight——让 LLM 自动为历史记忆生成语义索引
论文阅读·人工智能·学习·开源·github
guo_wen_qiang4 小时前
mac中docker desktop服务端开启远程访问
运维·服务器·macos·docker
Sayai5 小时前
Elasticsearch 9 安装验证实战:start-local 一键部署 + Docker 手动安装踩坑指南
大数据·elasticsearch·docker
小弥儿7 小时前
开源 VoiceStudio,声音克隆|配音|有声书一站式本地完成
学习·开源·github
名字还没想好☜11 小时前
Docker 容器安全加固实战:非 root、只读根文件系统、drop capabilities 与最小攻击面
运维·安全·docker·容器·kubernetes