CI 里用一个常见的场景是将代码提交后,打上 tag,CI 就可以检查代码并且自动构建镜像。
但是在 CI 里构建镜像比较复杂,因为 CI 的任务一般是运行在容器里,而需要在一个容器里运行 docker build,就需要在容器里再启一个 docker daemon 进程。这种形式叫 DinD(docker in docker)。
DinD 模式有一些问题

  • 安全问题,支持 DinD 模式的容器必须以特权模式运行
  • 性能问题,两层驱动,导致磁盘使用效率低下

基于这个原因,出现了 DooD(docker outside of docker),将宿主机的 docker socket 挂载到容器里,容器里的 docker 客户端直接于宿主机的 docker daemon 进程通信。DooD 会比 DinD 好一些,但是还是有问题

  • 安全问题,容器里的 docker 客户端拥用宿主机 docker daemon 进成的所有权限,也有些危险
  • Docker 构建上下文路径问题,如果在容器里用 git clone 一些文件用于构建,但是实际的构建是宿主机上的 docker daemon 进程,它的路径里可能没有这些文件,会导致构建失败。

所以人们开始尝试一种不需要 docker daemon 进程的构建方法,毕竟我们只需要构建一个镜像就行,不是真的要运行 docker 容器。基于此,出现了一些产品。比较出名的有 kanikobuildahbuildkit。今年(2025年)kaniko 已经归档了,buildah 看起来开发也不是很活跃,而 buildkit 是 moby 和 docker 一起开发的产品,估计以后的市场就是 buildkit 的了。
buildkit 被称为是下一代的 docker 构建工具,提升了构建速度,同时提供了一种 daemonless 的构建方式,非常适合在 CI 里使用。
在 CI 里配置 buildkit 需要注意以下几项内容。

使用 buildkit 代替 docker build

指定镜像,改掉 entrypoint

  image:
    name: moby/buildkit:rootless
    entrypoint: [""]

默认情况下,这个镜像启动时就会启动一个 daemon 进程。如果不改掉 entrypoint,CI 就会卡在 running server on /run/user/1000/buildkit/buildkitd.sock 这样一条日志上。
指定构建环境变量

  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox

启用这个命令不让 buildkit 再造一个沙合来执行 docker 的命令。
配置私有仓库权限

before_script:
  - mkdir -p ~/.docker
  - |
    echo "{
      \"auths\": {
        \"${CI_REGISTRY}\": {
          \"auth\": \"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"
        },
        \"$(echo -n $CI_DEPENDENCY_PROXY_SERVER | awk -F[:] '{print $1}')\": {
          \"auth\": \"$(printf "%s:%s" ${CI_DEPENDENCY_PROXY_USER} "${CI_DEPENDENCY_PROXY_PASSWORD}" | base64 | tr -d '\n')\"
        }
      }
    }" > ~/.docker/config.json    

创建一个配置文件,将私有仓库的账号填进去。

配置 dockerhub 代理

  before_script:
    - cat <<'EOF' > /tmp/buildkit.toml
      [registry."docker.io"]
        mirrors = ["mirror.example.com"]
      EOF

修改 docker 构建命令

script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true      

将 docker build 命令改成上面的命令,注意修改镜像名称,另外最后也可以配置要不要 push。

配置缓存

待补充

build 参数详解

待补充

参考

https://docs.gitlab.com/ci/docker/using_buildkit/