欢迎光临
我们一直在努力

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

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

每次改完代码,你都要经历这套"手动三连":本地 docker build → docker 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 用户名 通常是 root 或 ubuntu
SERVER_SSH_KEY SSH 私钥内容 cat ~/.ssh/id_rsa(服务器配好公钥后)

4.2 配置步骤

  • 打开你的 GitHub 仓库页面

  • 进入 Settings → Secrets and variables → Actions

  • 点击 New repository secret

  • 逐个填入上面的 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 ActionsGitLab CIJenkins
    配置文件 .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 install 或 apt-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 这三大核心技术,将揭开容器"看起来像虚拟机,其实不是"的秘密。理解这些原理,你在排查容器问题时就不再是"黑盒调试"了。


    参考资料

    • GitHub Actions 官方文档

    • docker/build-push-action 仓库

    • docker/login-action 仓库

    • appleboy/ssh-action 仓库

    • GitHub Actions 缓存文档

    赞(0)
    未经允许不得转载:171主机测评 » 14. CI/CD 流水线中集成 Docker:GitHub Actions 自动构建部署
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址