最近为了省钱,把一些项目从 CircleCI 迁到 GitHub Actions 时,发现一件反直觉的事:在 CircleCI 上用 Docker executor 比 machine executor 快 30% 以上,但在 GitHub Actions 上加了 container: 反而比直接跑在 Ubuntu 上慢了一截。两边都是用 Docker 容器,为什么体验会完全相反?

两种执行环境

CI 平台给我们「跑命令的地方」有两种:给一台虚拟机(每次 job 新起或恢复 VM)或 在已有机器上起容器(Host 一直跑,job 来了起个容器)。CircleCI 的 machine executor 是前者,docker executor 是后者。

GitHub Actions 特殊:runs-on: ubuntu-latest 看起来像 VM,实际是从池子分配的、已就绪的 hosted runner(预装环境,启动快);加 container: 就是在这台 runner 上又起一层容器。

环境类型CircleCIGitHub Actions特点
虚拟机machine: trueself-hosted runner(自建)新起 VM,慢
容器docker: - image: node:20container: { image: node:20 }在 host 上起容器
平台托管优化runs-on: ubuntu-latest池化预热,快

CircleCI:Docker 为什么更快

Node 项目对比:machine executor 跑完构建 3 分 20 秒,docker executor 降到 2 分 40 秒。

Machine 慢在启动 VM:新起或从快照恢复,再装 Node、跑 npm installDocker 快在复用 host:镜像缓存、十几秒起容器,环境就绪。

跑测试更明显:machine 要 docker-compose up 等数据库初始化,docker executor 的 service 容器并行启动,测试起来时数据库已经在听端口。

GitHub Actions:Docker 反而慢了

同样的项目,ubuntu-latest 直接跑 1 分 40 秒,加 container: 后涨到 2 分 30 秒。

Runner 本身就很轻:机器已就绪、Docker daemon 在跑、actions/setup-node 几秒准备好环境和 cache,温热机器直接开跑。

container: 是额外开销:拉镜像、起容器、挂载 workspace。CircleCI 上这是「比 VM 轻」,GitHub 上是「给轻流程加负担」。

在容器里执行 docker build

Job 跑在容器里又要 docker build(构建镜像推 registry),会遇到「容器里没 Docker daemon」的问题。

CircleCI:setup_remote_docker

CircleCI 在另一个地方起 remote Docker environment 有 daemon,job 容器通过 DOCKER_HOST 连过去:

- setup_remote_docker:
    version: 20.10.14
    docker_layer_caching: true
 
### GitHub Actions:两种方法
 
**直接用 runner**(推荐):`ubuntu-latest` 自带 Docker,直接 `run: docker build` 或用 `docker/build-push-action`。
 
**用 service 容器**:坚持用 `container:` 就得自己搭 dind:
 
```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    container: node:20
    services:
      docker:
        image: docker:dind
        options: --privileged
    steps:
      - run: docker build -t myapp .
        env:
          DOCKER_HOST: tcp://docker:2375

怎么选

场景CircleCIGitHub Actions为什么
标准语言环境docker executorubuntu-latestCircleCI docker 比 machine 轻;GitHub runner 已优化
数据库/中间件docker + serviceubuntu-latest + serviceGitHub 不需要额外 job container
构建镜像docker + setup_remote_dockerubuntu-latestRunner 自带 Docker
锁定环境docker + 自定义镜像container: + 自定义镜像用容器固定版本
跨平台测试machine(分 job)ubuntu-latest + macos-latest分 job 各跑

看起来相反的「最佳实践」,背后逻辑一致:在给定基础设施上选最轻的路径。CircleCI 的 machine 重,docker 快;GitHub 的 runner 轻,加容器反而是负担。