Buildx 多机多架构构建
手上有 amd64 和 arm64 多台机器时,最理想的多架构构建方式是:每台机器原生构建自己架构的镜像,最后合成一个多架构镜像。比在单机上用 QEMU 模拟构建快得多,也避免模拟环境下的各种诡异问题。
实现方式就是 buildx 的 remote driver:在每台机器上跑一个 buildkitd,本机用 docker buildx create --append 把它们组进同一个 builder,构建时 buildx 自动把每个平台调度到对应架构的节点上。
三种多架构构建方式对比
| 方式 | 速度 | 适用场景 |
|---|---|---|
| 单机 + QEMU 模拟 | 慢(模拟执行指令) | 偶尔构建一次、机器只有一台 |
| 多机 remote driver(本文) | 快(全部原生构建) | 常做多架构发布、手上有不同架构的机器 |
| 分架构推送 + imagetools 合并 | 快 | 机器间不能组网、CI 里不同架构的 runner |
--append 到底是哪个命令和追加相关的有两个:docker buildx create --append 是给 builder 追加构建节点(本文的主角);docker buildx imagetools create --append 是往已有 manifest 里追加平台(备选方案里会用到)。不要混淆。
整体架构

本机只负责下发构建任务和拼装结果,实际编译都发生在远端节点上,所以远端机器要能访问镜像仓库(推镜像)和基础镜像源(拉镜像)。
前置条件
- 每台远端机器:已安装 Docker(用来跑 buildkitd 容器)
- 本机:已安装 Docker 和 buildx(
docker buildx version确认) - 本机到各远端机器的 1234 端口网络可达
- 所有机器都能访问目标镜像仓库
第一步:远端机器生成 TLS 证书
buildkitd 走 TCP 暴露服务,必须用 TLS 加密和鉴权。在每台远端机器上执行(官方提供的 bake 目标,SAN 里写本机 IP):
SAN="192.168.10.10 localhost 127.0.0.1" docker buildx bake "https://github.com/moby/buildkit.git#master:examples/create-certs"
执行后当前目录生成 .certs/:
.certs/
├── daemon/ # 服务端证书,留在本机给 buildkitd 用
│ ├── ca.pem
│ ├── cert.pem
│ └── key.pem
└── client/ # 客户端证书,稍后拷给本机(发起构建的机器)
├── ca.pem
├── cert.pem
└── key.pem
不配 TLS 直接把 buildkitd 暴露在 TCP 上,等于把这台机器的 root 级构建权限开放给任何能连上端口的人。仅限完全可信的隔离网络才可以省略。
第二步:远端机器启动 buildkitd
每台远端机器上执行:
docker run -d \
--name buildkitd \
--restart unless-stopped \
--privileged \
-p 1234:1234 \
-v $PWD/.certs:/etc/buildkit/certs \
moby/buildkit:latest \
--addr tcp://0.0.0.0:1234 \
--tlscacert /etc/buildkit/certs/daemon/ca.pem \
--tlscert /etc/buildkit/certs/daemon/cert.pem \
--tlskey /etc/buildkit/certs/daemon/key.pem
确认运行正常:
docker logs buildkitd --tail 20
第三步:拷贝客户端证书到本机
每台远端机器各自生成了一套 CA,客户端证书要分开存放。在本机执行:
mkdir -p certs/amd64 certs/arm64
scp -r root@192.168.10.10:$PWD/.certs/client/. certs/amd64/
scp -r root@192.168.10.11:$PWD/.certs/client/. certs/arm64/
第四步:创建 builder 并追加节点
在本机执行。先创建 builder 并加入第一个节点(amd64 机器):
docker buildx create --name multi-arch --driver remote \
--node amd64 \
--driver-opt cacert=$PWD/certs/amd64/ca.pem,cert=$PWD/certs/amd64/cert.pem,key=$PWD/certs/amd64/key.pem \
tcp://192.168.10.10:1234
然后用 --append 追加 arm64 节点:
docker buildx create --name multi-arch --driver remote --append \
--node arm64 \
--driver-opt cacert=$PWD/certs/arm64/ca.pem,cert=$PWD/certs/arm64/cert.pem,key=$PWD/certs/arm64/key.pem \
tcp://192.168.10.11:1234
启用并检查:
docker buildx use multi-arch
docker buildx inspect --bootstrap
输出里应该看到两个节点,各自带上自己架构的平台:
Name: multi-arch
Driver: remote
Nodes:
Name: amd64
Endpoint: tcp://192.168.10.10:1234
Status: running
Platforms: linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/386
Name: arm64
Endpoint: tcp://192.168.10.11:1234
Status: running
Platforms: linux/arm64*, linux/arm/v8, linux/arm/v7, linux/arm/v6
节点的平台列表是连接时自动探测的,一般不用手动指定。只有多个节点都支持同一平台、想人为分配时,才在 create 时用 --platform 固定。
第五步:构建多架构镜像
在本机项目目录下,一条命令构建两个架构并推送:
docker buildx build --platform linux/amd64,linux/arm64 \
-t registry.example.com/myapp:1.0.0 \
--push .
buildx 会按平台把构建任务调度到对应节点:amd64 部分发给 amd64 节点、arm64 部分发给 arm64 节点,两边都是原生速度并行编译,最后自动拼成 manifest list 推到仓库。
验证镜像
docker buildx imagetools inspect registry.example.com/myapp:1.0.0
能看到一个 tag 下挂了两个平台的 manifest 就说明成功了:
Name: registry.example.com/myapp:1.0.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:xxxx
Manifests:
Name: registry.example.com/myapp:1.0.0@sha256:aaaa
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/amd64
Name: registry.example.com/myapp:1.0.0@sha256:bbbb
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/arm64
常见问题
context deadline exceeded,节点连不上
按顺序查:
- 本机到远端的 1234 端口是否可达:
nc -zv 192.168.10.10 1234 - 证书的 SAN 里是否包含你连接用的 IP 或域名(第一步生成时写对没有)
- 如果连接地址和证书里的名字不一致,用
--driver-opt servername=证书里的名字指定 TLS 校验用的服务器名 docker logs buildkitd --tail 50看远端守护进程有没有报错
远端拉取基础镜像很慢
构建是在远端节点上执行的,拉基础镜像走的也是远端的网络。在远端机器的 buildkitd 里配镜像加速(写 buildkitd.toml 的 registry.mirrors,启动时用 --config 挂载),不要只配本机的。
要移除某个节点或整个 builder
# 从 builder 移除一个节点
docker buildx create --name multi-arch --node arm64 --leave
# 删除整个 builder
docker buildx rm multi-arch
备选方案:分架构推送再合并
如果机器之间不能组网(比如 CI 里不同架构的 runner),可以各自本地构建、推不同标签,最后合并:
# amd64 机器上
docker buildx build --platform linux/amd64 -t registry.example.com/myapp:1.0.0-amd64 --push .
# arm64 机器上
docker buildx build --platform linux/arm64 -t registry.example.com/myapp:1.0.0-arm64 --push .
# 任意一台能访问仓库的机器上合并成一个 tag
docker buildx imagetools create -t registry.example.com/myapp:1.0.0 \
registry.example.com/myapp:1.0.0-amd64 \
registry.example.com/myapp:1.0.0-arm64
如果镜像已存在、只想往里加一个平台,用 docker buildx imagetools create --append -t 已有标签 新平台镜像 即可,不用把所有源重新列一遍。