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 仓库:
然后在本地使用已安装的 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 也支持设置容器的环境变量和暴露端口,分别使用 env
和 ports
关键字:
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:
在 job 详情页中可以看到容器初始化过程和步骤执行结果:
可以看到,步骤在容器中成功执行,并输出了预期的 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 运行:
job 成功执行后,可以看到输出日志中打印了 Hello Baeldung
:
整个流程中,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
输出的内容:
说明该 step 确实是在容器中执行的。
7. 小结
在本文中,我们学习了两种在 GitHub Actions 中使用 Docker 的方式:
- 为整个 job 指定容器运行环境
- 在 step 中使用容器化的 action
这两种方式各有适用场景,前者适合需要统一环境的完整任务流程,后者则适合模块化、可复用的小型容器任务。
✅ 总结:GitHub Actions 结合 Docker 提供了灵活的工作流构建能力。无论是全容器化的 job,还是基于容器的 action,都能显著提升构建、测试和部署流程的一致性和可维护性。