1. 简介

GitHub Actions 是一个可以在 GitHub 上直接自动化软件开发流程的工具。它非常适合用于自动化构建、测试和部署流程,也就是我们常说的持续集成与持续交付(CI/CD)。

GitHub Actions 的工作流(workflow)定义了任务(job),任务又被拆分为多个步骤(step)。这些步骤通常通过运行脚本或使用预构建的 action 来完成。默认情况下,job 是在 runner 的主机环境中运行的。但如果我们使用容器化的 job 或 step,则会增加一层隔离性。每个 job 或 step 会在各自的 Docker 容器中运行,确保所需的工具和依赖都已就位,同时不会影响到 runner 本机。

在本文中,我们将探讨在 GitHub Actions 中使用 Docker 的多种方式。所有示例命令均在 Ubuntu 22.04 上测试通过。

2. 为什么要在 GitHub Actions 中使用 Docker 容器

在 GitHub Actions 工作流中使用 Docker 容器有以下几个优势:

环境一致性:避免因 runner 机器缺少依赖或版本冲突导致的问题,确保代码始终在相同且可预测的环境中运行
可重复构建:容器镜像完整封装了构建环境,使得本地、CI 服务器或其他任何环境下的构建结果一致
任务隔离:防止不同任务之间因使用不同工具或库而产生冲突,保护 runner 机器不受任务内部问题影响
简化工作流:无需手动管理 runner 上的依赖,只需指定所需环境的 Docker 镜像,使工作流更声明式、易维护

总的来说,Docker 容器为 GitHub Actions 提供了一种实现一致、隔离和可复用工作流的有效方式。

3. GitHub Actions 中使用 Docker 的入门

GitHub Actions 中使用 Docker 有两种主要方式:

  • 为整个 job 指定一个 Docker 容器
  • 在某个 step 中引用一个配置为在容器中运行的 action

无论使用哪种方式,都可以获得隔离的运行环境。需要注意的是,必须使用 Linux 类 runner 才能在 GitHub Actions 中使用 Docker 容器

为演示方便,我们先创建一个名为 docker-github-actions 的 GitHub 仓库:

Github Repository for Docker in Github Actions

然后在本地使用已安装的 git 克隆该仓库:

$ git clone https://github.com/<username>/docker-github-actions.git

进入项目目录并创建 .github/workflows 文件夹:

$ cd docker-github-actions
$ mkdir -p .github/workflows

GitHub Actions 的工作流文件都必须放在 .github/workflows 目录下,这是 GitHub 识别和执行工作流的前提条件

4. 为整个 GitHub Actions Job 使用 Docker 容器

创建好 workflows 目录后,我们可以定义一个完整的 job 在容器中运行的工作流。

这种方式是在 job 中指定一个容器,然后所有步骤都运行在该容器内部。在 GitHub 的 workflow YAML 文件中,我们使用 jobs 关键字定义 job,然后在 job 中使用 container 指定容器。

4.1. 在 YAML 工作流中定义容器 job

⚠️ 注意:仅仅在 job 中使用 container 关键字,并不意味着步骤就一定运行在容器中。要让步骤运行在容器中,必须同时指定 runner 主机和容器镜像

jobs:
  container-test-job:
    runs-on: ubuntu-latest
    container: node:18

上面的例子中,runner 主机是 ubuntu-latest,容器镜像是 node:18。如果容器镜像仓库需要认证,可以使用 credentials 关键字提供用户名和密码

container:
  image: ghcr.io/owner/image
  credentials:
     username: ${{ github.actor }}
     password: ${{ secrets.github_token }}

GitHub Actions 也支持设置容器的环境变量和暴露端口,分别使用 envports 关键字:

container:
  image: python:3.9
  env:
     TEST_VAR: "This is a test environment variable"
  ports:
      - 8000:8000

另外,也可以使用 volumes 挂载卷到容器中,实现与主机的数据共享或多个步骤之间的数据传递:

volumes:
  - my_docker_volume:/volume_mount
  - /data/my_data
  - /source/directory:/destination/directory

如上例所示,源路径和目标路径之间使用冒号 : 分隔。

4.2. 在容器中运行 job 示例

我们来创建一个使用 Node.js 17.6.0 容器的 job,用于验证容器中的 Node.js 和 npm 版本。

在 workflows 目录下创建 container-job.yaml 文件,并写入以下内容:

on: push
jobs:
    container-job:
        runs-on: ubuntu-latest
        container: node:17.6.0
        steps:
            - run: node --version
            - run: npm --version

提交并推送配置到远程仓库:

$ git add .
$ git commit -m "Run Node 17"
$ git push

此时 GitHub Actions 会触发一次新的 workflow 运行,提交信息为 Run Node 17

Workflow Run with Commit Message: Run Node 17

在 job 详情页中可以看到容器初始化过程和步骤执行结果:

Container-Job Container Initialization Process and Step Execution

可以看到,步骤在容器中成功执行,并输出了预期的 Node.js(17.6.0)和 npm 版本。

5. 使用 Dockerfile 在 step 中运行容器 action

Dockerfile 方式是指 GitHub Actions 根据提供的 Dockerfile 构建镜像,再从该镜像启动容器来执行任务。

要实现一个基于 Dockerfile 的 action,我们需要创建两个文件:

  • action 的元数据 YAML 文件(action.yml)
  • 容器启动时执行的脚本代码(entrypoint.sh)

GitHub Actions 中使用 uses 关键字调用该 action

5.1. 创建 Dockerfile

在项目根目录下创建 Dockerfile,并写入以下内容:

FROM alpine:3.10

COPY entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

该 Dockerfile 基于 Alpine Linux 构建,将 entrypoint.sh 脚本复制进容器,并在容器启动时运行该脚本。

5.2. 创建 action 元数据文件

创建 action.yml 文件,定义输入输出和执行方式:

# action.yml
name: 'Hello Baeldung'
description: 'Greet Baeldung and record the time'
inputs:
  who-to-greet:  # id of input
    description: 'Who to greet'
    required: true
    default: 'Baeldung'
outputs:
  time: # id of output
    description: 'The time we greeted Baeldung'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.who-to-greet }}

这个元数据文件告诉 GitHub Actions 如何构建并运行该容器 action。它定义了输入参数 who-to-greet 和输出参数 time,并通过 Dockerfile 构建镜像来运行容器。

5.3. 编写入口脚本 entrypoint.sh

创建 entrypoint.sh 文件,内容如下:

#!/bin/sh -l

echo "Hello $1"
time=$(date)
echo "time=$time" >> $GITHUB_OUTPUT

保存后将其设为可执行:

$ chmod u+x entrypoint.sh

这样容器启动时就能运行该脚本。

5.4. 在 workflow 中测试该 action

.github/workflows/ 下创建 step-action.yml 文件:

on: [push]

jobs:
  hello_baeldung_job:
    runs-on: ubuntu-latest
    name: A job to say hello to Baeldung
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Hello Baeldung action step
        uses: ./ # Uses an Docker container action in the root directory
        id: hello
        with:
          who-to-greet: 'Baeldung'
      - name: Get the output time
        run: echo "The time was ${{ steps.hello.outputs.time }}"

提交并推送所有文件:

$ git add .
$ git commit -m "Docker container step action"
$ git push

随后在 GitHub Actions 中会看到一次新的 workflow 运行:

A Job to Say Hello to Baeldung

job 成功执行后,可以看到输出日志中打印了 Hello Baeldung

A Job to Say Hello to Baeldung Step Action and Echo Output

整个流程中,GitHub Actions 自动构建镜像、启动容器并执行 action,整个过程自动化完成。

6. 使用 addnab/docker-run-action 在 step 中运行容器

除了从零开始构建容器 action,GitHub Marketplace 上也提供了许多现成的容器 action。下面我们演示使用 addnab/docker-run-action 来在 step 中运行容器。

6.1. 创建 workflow

我们创建一个使用 nginx:latest 镜像运行 echo 命令的 workflow。

.github/workflows/ 下创建 public-action.yml 文件:

name: Run step in Docker

on: [push]

jobs:
    container-job:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout code
              uses: actions/checkout@v2

            - name: Run step in container
              uses: addnab/docker-run-action@v3
              with:
                image: nginx:latest
                run: |
                  echo "This step is using a Public Docker Container Action "

6.2. 测试 workflow

提交并推送该 workflow:

$ git add .
$ git commit -m "Public Docker container action"
$ git push

在 GitHub Actions 页面中可以看到新触发的 workflow,job 成功执行后,可以在日志中看到 echo 输出的内容:

addnab/docker-run-action Echo Log Output

说明该 step 确实是在容器中执行的。

7. 小结

在本文中,我们学习了两种在 GitHub Actions 中使用 Docker 的方式:

  • 为整个 job 指定容器运行环境
  • 在 step 中使用容器化的 action

这两种方式各有适用场景,前者适合需要统一环境的完整任务流程,后者则适合模块化、可复用的小型容器任务。

总结:GitHub Actions 结合 Docker 提供了灵活的工作流构建能力。无论是全容器化的 job,还是基于容器的 action,都能显著提升构建、测试和部署流程的一致性和可维护性。


原始标题:How to Use Docker With GitHub Actions