在 Docker 容器内构建 Docker 镜像:Go 程序的嵌套构建实践指南

1次阅读

在 Docker 容器内构建 Docker 镜像:Go 程序的嵌套构建实践指南

本文详解如何在 docker 容器中安全、可靠地构建另一个 docker 镜像(即“docker-in-docker”),重点解决 go 应用多阶段构建中因缺少 docker 运行时而导致最终镜像无法生成的问题,并提供可落地的权限配置与最佳实践。

本文详解如何在 docker 容器中安全、可靠地构建另一个 docker 镜像(即“docker-in-docker”),重点解决 go 应用多阶段构建中因缺少 docker 运行时而导致最终镜像无法生成的问题,并提供可落地的权限配置与最佳实践。

在构建 Go 应用的容器化交付流程时,一个常见但易被忽视的误区是:将 docker build 命令写入 Dockerfile 的 CMD 指令中。正如示例所示,虽然构建过程看似成功(输出 Successfully built d91a908ee663),但该哈希值仅对应最外层构建器镜像(即 google/golang 基础镜像构建出的构建环境),而非目标运行镜像 foobar——因为 CMD 仅定义容器启动时执行的命令,它不会在构建阶段自动触发,也不会将子镜像持久化到宿主机 Docker daemon 中。

要真正实现“容器内构建容器”,必须满足两个前提条件:

  1. Docker CLI 可用:构建容器内需安装 docker 二进制文件;
  2. Docker daemon 可访问:容器需能与宿主机 Docker 守护进程通信(通常通过挂载 /var/run/docker.sock)。

✅ 正确做法是:分离构建逻辑与镜像定义,使用 docker run 显式启动构建容器,并注入必要资源

✅ 推荐方案:基于挂载 socket 的轻量级 DinD(无需完整 Docker-in-Docker)

首先,修正原始 Dockerfile(建议改名为 Dockerfile.builder),移除无效的 CMD docker build,专注编译 Go 程序并准备构建上下文:

# Dockerfile.builder FROM golang:1.22-alpine AS builder WORKDIR /app COPY ./src/ . RUN CGO_ENABLED=0 GOOS=linux go build -a -tags rs -ldflags '-w -s' -o mygoprog .  # 使用多阶段构建,避免暴露构建工具链 FROM scratch COPY --from=builder /app/mygoprog /mygoprog EXPOSE 9100 ENTRYPOINT ["/mygoprog"]

? 注:现代 Go 构建推荐使用 Alpine + scratch 的多阶段方式,比原方案更安全、更小(无需 google/golang 镜像或额外 docker 二进制)。

若仍需在构建容器中执行 docker build(例如动态生成配置、集成测试镜像等),则按如下方式启动构建容器:

# 确保当前目录含目标 Dockerfile(如 ./src/Dockerfile) docker build -t mygoprog-builder -f Dockerfile.builder .  # 启动构建容器,挂载 Docker socket 和 CLI(适配 Linux/macos) docker run    --rm    -v "$(pwd)/src:/workspace"    -v /var/run/docker.sock:/var/run/docker.sock    -v $(which docker):/usr/bin/docker    -w /workspace    mygoprog-builder    sh -c "docker build -t foobar ."

⚠️ 关键注意事项

  • /var/run/docker.sock 挂载赋予容器对宿主机 Docker daemon 的完全控制权,请仅在可信、隔离环境中使用;
  • macOS(Docker Desktop)和 windows(WSL2)路径可能不同,/var/run/docker.sock 通常仍有效,但 CLI 路径建议用 $(which docker) 动态获取;
  • 如遇权限错误(如 Cannot connect to the Docker daemon),可尝试添加 –privileged(不推荐)或确保用户属于 docker 组(宿主机侧);
  • docker build 命令中的上下文路径(.)需指向包含目标 Dockerfile 的目录(如 ./src),否则会报错 unable to prepare context。

✅ 更优替代:完全避免 DinD —— 使用 BuildKit 多阶段构建

对于绝大多数 Go 应用,根本无需 DinD。直接在单个 Dockerfile 中完成编译与打包:

# 推荐:单文件、无特权、零依赖的终极方案 # Dockerfile FROM golang:1.22-alpine AS builder WORKDIR /app COPY ./src/*.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -tags rs -ldflags '-w -s' -o mygoprog .  FROM scratch COPY --from=builder /app/mygoprog /mygoprog EXPOSE 9100 ENTRYPOINT ["/mygoprog"]

构建命令极简:

docker build -t foobar . docker run --rm -p 9100:9100 foobar

✅ 优势:无需挂载 socket、无权限风险、镜像体积最小(

总结:所谓“Docker-in-Docker”并非必须,而应视为特定场景下的权衡选择。对 Go 应用而言,多阶段构建是默认首选;仅当需动态生成 Dockerfile 或跨平台构建时,才谨慎启用 socket 挂载模式,并始终遵循最小权限原则。

text=ZqhQzanResources