Moby 引擎构建与交叉编译实战:基于 Docker Buildx Bake 的 docker-bake.hcl 全解
本篇基于 ctn-build.md 贡献指南展开,讲解 Moby(Docker Engine 上游项目)如何利用根目录的 Dockerfile 与 docker-bake.hcl 构建定义,通过 docker buildx bake 完成 dockerd 及辅助工具的二进制构建与多平台交叉编译,并逐条拆解 bake 变量、target 与 Makefile、hack/make.sh 底层脚本的协作关系,读完即可在本地复现官方 CI 同款构建链路。
构建体系总览
Moby 的构建体系围绕三个文件组织:
- Dockerfile:根目录多阶段构建文件,声明了 Go 工具链、交叉编译助手
tonistiigi/xx、以及 containerd、runc、tini、rootlesskit、crun、containerutility 等依赖组件的独立构建阶段; - docker-bake.hcl:Buildx Bake 定义文件,声明了所有构建变量、target 与平台矩阵,是
docker buildx bake命令的“配方”; - Makefile:把
docker buildx bake封装成make binary、make cross等便捷入口,并负责把本地环境变量透传进构建容器。
正如 ctn-build.md 所述:Dockerfile 支持使用 Docker Buildx 与 BuildKit 构建和交叉编译 docker daemon 及额外工具,而 bake 定义正是为了简化这一过程而存在的。文档同时指出,set-up-dev-env.md 中“development container”一节介绍了如何在 Linux 容器中开发和编译改动,而二进制构建与交叉编译则由 bake 命令直接完成。
核心构建命令全解
以下是 ctn-build.md 给出的全部官方命令,按用途分组:
# build binaries for the current host platform
# output to ./bundles/binary-daemon by default
docker buildx bake
# or
docker buildx bake binary
# build binaries for the current host platform
# output to ./bin
DESTDIR=./bin docker buildx bake
# build dynamically linked binaries
# output to ./bundles/dynbinary-daemon by default
DOCKER_STATIC=0 docker buildx bake
# or
docker buildx bake dynbinary
# build binaries for all supported platforms
docker buildx bake binary-cross
# build binaries for a specific platform
docker buildx bake --set *.platform=linux/arm64
# build "complete" binaries (including containerd, runc, etc.)
docker buildx bake all
# build "complete" binaries for all supported platforms
docker buildx bake all-cross
# build non-runnable image wrapping "complete" binaries
# useful for use with undock and sharing via a registry
docker buildx bake bin-image
# build non-runnable image wrapping "complete" binaries, with custom tag
docker buildx bake bin-image --set "*.tags=foo/moby-bin:latest"
# build non-runnable image wrapping "complete" binaries for all supported platforms
# multi-platform images must be directly pushed to a registry
docker buildx bake bin-image-cross --set "*.tags=foo/moby-bin:latest" --push
逐条说明:
docker buildx bake(不带 target)等价于bake binary。原因在 docker-bake.hcl 中:group "default"的targets = ["binary"],bake 默认执行 default group。- 输出目录由
DESTDIR变量控制。docker-bake.hcl 定义了DESTDIR变量和一个bindir函数:DESTDIR != "" ? DESTDIR : "./bundles/${defaultdir}",所以默认落在./bundles/binary-daemon(静态)或./bundles/dynbinary-daemon(动态),设置DESTDIR=./bin后则落到./bin。 DOCKER_STATIC=0生成动态链接二进制,与bake dynbinary等价。默认DOCKER_STATIC=1,即静态构建;动态目标会输出到bundles/dynbinary-daemon。binary-cross覆盖所有支持平台。平台清单定义在 docker-bake.hcl 的_platformstarget 中:linux/amd64、linux/arm/v5、linux/arm/v6、linux/arm/v7、linux/arm64、linux/ppc64le、linux/s390x、windows/amd64。--set *.platform=linux/arm64是 Buildx bake 的变量覆盖语法,可把任意 target 的平台收窄到单个平台做定向构建(例如只构建 Windows 版)。all/all-cross构建“完整”二进制集,除 dockerd、docker-proxy 外还打包 containerd、runc 等配套工具,对应 Dockerfile 中的all阶段。bin-image/bin-image-cross把“完整”二进制封装进一个不可运行(scratch 基底)的镜像,便于配合undock解包分发,或直接推送到 registry 共享。bin-image-cross使用type=image输出,多平台镜像必须--push直接推送(因为多平台 manifest 本地无法落盘)。
深入 docker-bake.hcl:变量与 Target 矩阵
构建变量
docker-bake.hcl 顶部声明了以下变量,均可通过环境变量注入:
| 变量 | 默认值 | 作用 |
|---|---|---|
DOCKER_DEBUG |
"" |
非空时开启调试:hack/make.sh 会加 -gcflags="all=-N -l" 禁用内联与优化,并打印构建命令 |
DOCKER_STATIC |
"1" |
1 为静态链接,0 为动态链接(影响 dockerd 本体与 containerd、runc 等组件的构建方式) |
DOCKER_LDFLAGS |
"" |
追加给 go build -ldflags,可用于构建期注入值(见下文 Makefile 的 graphdriver 优先级示例) |
DOCKER_BUILDTAGS |
"" |
追加 Go build tag |
DOCKER_GITCOMMIT |
null |
指定写入二进制的 git 提交号 |
VERSION |
"" |
Docker 版本号,如 23.0.0-dev,通常由 Git ref 自动生成 |
PLATFORM |
"" |
平台名,如 Docker Engine - Community |
PRODUCT |
"" |
产品名,用于 BuildKit 的 ExportedProduct,在不支持某特性的版本上给出友好报错 |
DEFAULT_PRODUCT_LICENSE |
"" |
对应 version.DefaultProductLicense,可放置商业引擎的许可证摘要 |
PACKAGER_NAME |
"" |
打包者名称,用于 Windows 清单的 CompanyName |
DESTDIR |
"" |
覆盖输出目录(见上) |
SYSTEMD / FIREWALLD |
false |
仅用于 dev target,决定开发容器是否安装 systemd、firewalld |
GOVULNCHECK_FORMAT |
null |
govulncheck target 的输出格式 |
所有核心变量(除 DESTDIR 外的前 11 个)都被 _common target(docker-bake.hcl)以 args 形式统一注入 Dockerfile 构建,实现“一处声明、全局生效”。
Target 依赖关系
从源码结构看,各 target 通过 inherits 形成清晰的继承链:
binary:继承_common,构建 Dockerfile 的binary阶段,输出bindir(DOCKER_STATIC == "1" ? "binary" : "dynbinary");dynbinary:继承binary,强制DOCKER_STATIC=0,输出固定为dynbinary目录;binary-cross:继承binary+_platforms,即“对全平台矩阵重复 binary 构建”;binary-smoketest:继承_common,构建smoketest阶段且output = ["type=cacheonly"](不落盘产物,仅验证可编译可运行);all:继承_common,构建all阶段(完整工具集),输出目录规则同binary;all-cross:all+_platforms;bin-image:继承all与docker-metadata-action(tags 为moby-bin:local),输出type=docker(本地镜像);bin-image-cross:继承bin-image,改type=image并枚举 7 个平台(含windows/amd64);dind:继承_common,构建dind阶段,tag 为docker-dind,输出本地镜像;dev:继承_common,构建dev阶段(即开发容器),额外透传SYSTEMD/FIREWALLD,tag 为docker-dev;govulncheck:使用独立的 hack/dockerfiles/govulncheck.Dockerfile,用于 Go 漏洞检查。
此外,default group 只包含 binary,这就是裸 docker buildx bake 的默认行为来源。
深入 Dockerfile:构建阶段如何工作
Dockerfile 是 bake 命令真正执行的构建脚本,关键设计点:
1. 交叉编译基础:tonistiigi/xx
Dockerfile 第一行即 FROM --platform=$BUILDPLATFORM tonistiigi/xx 阶段,把 xx 交叉编译工具集注入 base 阶段。之后各组件统一使用 xx-go build、xx-apt-get install、xx-verify 等命令完成目标平台的 Go 编译、依赖安装和产物校验——这正是 binary-cross 能“一次 bake 出八平台产物”的底层机制。基础镜像由 GO_VERSION=1.26.8 与 Debian bookworm 的 golang 官方镜像构成(Dockerfile)。
2. 配套组件的独立阶段
每个辅助工具都是一个“src + build + OS 选择”三段式:
- containerd:Dockerfile 从 containerd v2.3.4 源码构建
containerd、containerd-shim-runc-v2、ctr;DOCKER_STATIC=1时以make STATIC=1、CGO_ENABLED=0编译并xx-verify --static校验静态链接。Windows 平台则回退到binary-dummy空阶段。 - runc:Dockerfile 构建 runc v1.5.1,静态时执行
make static。 - tini(docker-init):Dockerfile 以 cmake 构建
tini-static,供容器--init使用。 - rootlesskit:Dockerfile 静态时用
CGO_ENABLED=0纯 Go 编译,动态时-linkmode=external;同时把 contrib/dockerd-rootless.sh 与 contrib/dockerd-rootless-setuptool.sh 拷入产物目录。 - crun / containerutility(Windows)/ criu:分别见 Dockerfile、Dockerfile、Dockerfile(criu 阶段当前因 opensuse 仓库稳定性被注释停用)。
3. build、binary 与 all 阶段
build(Dockerfile):安装 clang/lld 等依赖后,在$BUILDPLATFORM上执行PKG_CONFIG=$(xx-go env PKG_CONFIG) ./hack/make.sh "$target",其中DOCKER_STATIC=1时target=binary(静态),否则target=dynbinary;构建完成后用xx-verify校验/tmp/bundles/<target>-daemon/dockerd与docker-proxy,并把产物搬到/build/。ENV PREFIX=/tmp是为了规避只读工作目录导致的 make.sh 输出失败。binary(Dockerfile):FROM scratch,仅拷贝 dockerd + docker-proxy;all(Dockerfile):FROM scratch,额外COPY --link引入 tini、runc、containerd、rootlesskit、containerutility 各阶段的/build/产物——这正是bake all所称“complete binaries”的由来。smoketest(Dockerfile):在 base 上运行file dockerd && dockerd --version && docker-proxy --version,用于 CI 上binary-smoketest的冒烟验证。dind(Dockerfile):基于官方docker:dind镜像,把 cli、buildx、compose 插件与all阶段产物叠加进去,产出docker-dind开发用 DinD 镜像。
4. 从 make.sh 到 go build 的真实调用链
Dockerfile 的 build 阶段最终落到 hack/make.sh:
- hack/make.sh 的
bundle()函数会source hack/make/<bundle名>; - hack/make/binary-daemon 以
DOCKER_STATIC=1、GO_PACKAGE='github.com/moby/moby/v2/cmd/dockerd'、BINARY_NAME='dockerd'调用 hack/make/.binary,并在目标平台与宿主机一致时把 runc、containerd、docker-init 等“嵌套可执行文件”一并拷入 bundle 目录; - hack/make/.binary 处理了动态构建的细节:非静态时在多数平台追加
-buildmode=pie(mips*/ppc64 除外);hack/make/.binary 中 Windows 下移除netgotag 以使用系统 DNS 解析器;hack/make/.binary 针对 arm/v5 交叉编译补充-Wno-atomic-alignment与-latomic;hack/make/.binary 最终执行带-tags "netgo osusergo static_build nri_no_wasm ..."的go build,并把DOCKER_LDFLAGS拼入-ldflags。
版本号方面,hack/make.sh 会解析 VERSION(支持 tag/分支/PR ref 归一化),hack/make.sh 则取 DOCKER_GITCOMMIT 或 git rev-parse HEAD,工作区不干净时自动追加 -unsupported 后缀——这解释了 bake 变量为何要提供 VERSION 与 DOCKER_GITCOMMIT 两个注入点。
Makefile 封装与环境变量透传
不想手写 docker buildx bake 时,Makefile 提供了等价入口:
binary: $(BAKE_CMD) binary # 静态 linux 二进制
dynbinary: $(BAKE_CMD) dynbinary # 动态链接 linux 二进制
cross: $(BAKE_CMD) binary-cross # 全平台交叉构建
win: $(BAKE_CMD) --set *.platform=windows/amd64 binary
Makefile 中 default: binary,且 BAKE_CMD := $(BUILDX) bake,DOCKER_GITCOMMIT 在 Makefile 顶部即由 git rev-parse HEAD 计算并 export,因此 make binary 与 docker buildx bake binary 完全等价。Makefile 的 DOCKER_ENVS 列表把 DOCKER_LDFLAGS、VERSION、PLATFORM、PRODUCT 等变量以 -e 形式透传进运行容器——其中注释给出了一个典型用法(Makefile):
make DOCKER_LDFLAGS="-X github.com/moby/moby/v2/daemon/graphdriver.priority=overlay2,zfs" dynbinary
即在构建期通过 -ldflags -X 改写内置的 graphdriver 优先级列表。
值得注意的约束:Makefile 明确说明不能把 DOCKER_BUILDTAGS 加入 -e 列表,因为即使 shell 中未设置,-e 也会遮蔽 Dockerfile 内的 ENV DOCKER_BUILDTAGS,而后者对官方构建很重要。
输出位置与产物结构速查
综合 bake 定义与 Dockerfile,各命令的产物去向如下:
| 命令 | 构建阶段 | 产物/输出位置 |
|---|---|---|
docker buildx bake / bake binary |
binary |
./bundles/binary-daemon/(DESTDIR 覆盖之) |
docker buildx bake dynbinary |
binary(动态) |
./bundles/dynbinary-daemon/ |
DESTDIR=./bin docker buildx bake |
binary |
./bin/ |
docker buildx bake binary-cross / all-cross |
全平台矩阵 | 各平台对应目录(本地 type=local 输出) |
docker buildx bake all |
all |
./bundles/{binary,dynbinary}-daemon/,含 containerd、runc 等 |
docker buildx bake bin-image |
all |
本地镜像 moby-bin:local(scratch 基底,不可运行) |
docker buildx bake bin-image-cross --push |
all |
多平台 manifest 推送至 registry |
make win |
binary(windows/amd64) |
dockerd.exe 等 |
小结
Moby 的二进制构建遵循“Dockerfile 定义能力,docker-bake.hcl 编排场景,Buildx bake 触发执行”的三层结构:DOCKER_STATIC、DESTDIR、PLATFORM、VERSION 等变量贯穿 bake 与 Dockerfile 两个文件,_common target 保证了变量注入的一致性;binary/all scratch 阶段与 tonistiigi/xx 工具链支撑了八平台的静态/动态交叉编译,而 hack/make.sh 与 hack/make/.binary 则把 Go 编译细节(build tags、PIE、ARM 标志、版本注入)封装在 bake 背后。贡献者按 docs/contributing/ctn-build.md 的命令操作,即可与 CI 使用完全相同的构建链路产出可复现的引擎二进制。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00