Buildx 多架构镜像构建
同一份应用镜像可能需要运行在不同 CPU 上:常见的云服务器多为 linux/amd64,Apple silicon 开发机、树莓派和部分云实例则使用 linux/arm64。如果只构建一种架构,另一种机器拉取镜像时可能报 no matching manifest for linux/...,或只能通过性能较差的模拟运行。
Docker Buildx 可以在一次构建中生成多个架构的镜像,并将它们以**多架构清单(manifest list)**推送到镜像仓库。用户仍然只需拉取同一个镜像标签,Docker 会自动选择与当前机器匹配的镜像。
完成本篇后,你将能够:
- 确认 Buildx、BuildKit 与目标架构是否可用;
- 创建支持多架构的构建器;
- 将
amd64和arm64原生节点组合为一个构建器; - 一次构建并推送
linux/amd64和linux/arm64镜像; - 查看镜像 manifest,验证两个架构都已发布;
- 使用缓存加快 CI 构建,并定位常见失败原因。
1. 先理解多架构镜像
一个普通镜像标签通常只指向一种 CPU 架构的镜像,例如:
registry.example.com/team/demo:1.0
-> linux/amd64 镜像
多架构标签则指向一份 manifest list,其中包含多个平台镜像:
registry.example.com/team/demo:1.0
-> linux/amd64 镜像
-> linux/arm64 镜像
当 amd64 主机执行 docker pull registry.example.com/team/demo:1.0 时,会拉取 amd64 版本;arm64 主机使用相同命令时,会拉取 arm64 版本。标签相同,但镜像层和可执行文件可以不同。
本篇构建的是 Linux 容器镜像,因此平台写作 linux/amd64 和 linux/arm64。Windows 容器与 Linux 容器不能简单混合为同一份通用镜像,需使用匹配的 Windows 基础镜像和运行环境。
2. 准备 Buildx
Docker Desktop 和较新的 Docker Engine 通常已经自带 Buildx 插件。先确认版本和当前构建器:
# 确认 Docker Engine 正常运行
docker version
# 确认 Buildx 插件已安装
docker buildx version
# 查看现有构建器及其支持的平台
docker buildx ls
在 Ubuntu 上,如果 Buildx 不存在,可通过 Docker 官方软件源安装插件:
# 安装 Docker Buildx 插件;需要已配置 Docker 官方 APT 仓库
sudo apt-get update
sudo apt-get install -y docker-buildx-plugin
# 再次确认插件已可用
docker buildx version
2.1 创建独立构建器
默认 docker 驱动通常只能直接构建当前主机架构。多架构构建建议使用基于 BuildKit 容器的 docker-container 驱动:
# 创建并立即切换到名为 multiarch 的构建器
docker buildx create \
--name multiarch \
--driver docker-container \
--use
# 启动 BuildKit,并显示它实际支持的平台
docker buildx inspect --bootstrap
预期输出的 Platforms 至少包含当前架构;Docker Desktop 通常会同时显示 linux/amd64 和 linux/arm64。Linux 主机若只显示本机架构,继续阅读下一节配置模拟器。
multiarch 会在本机保留。之后的终端可执行 docker buildx use multiarch 重新选择它;查看所有构建器使用 docker buildx ls。
2.2 Linux 主机启用其他架构模拟
在 Linux 上,Buildx 使用 QEMU/binfmt 模拟非本机 CPU 指令。执行以下命令注册模拟器:
# 注册常用架构的 binfmt 处理器;需要访问 Docker Hub
docker run --privileged --rm tonistiigi/binfmt --install all
# 重新启动或新建构建器后,确认支持的目标平台
docker buildx inspect --bootstrap
--privileged 会修改宿主机的 binfmt 配置,只应在可信的 Docker 主机上执行。模拟构建的速度通常慢于原生构建,复杂的编译任务更适合交给对应架构的 CI Runner 或远程原生节点。
2.3 使用多个 AMD64 和 ARM64 节点分工构建
生产 CI 更推荐使用原生节点,而不是让其中一台机器通过 QEMU 模拟另一种架构。一个 Buildx builder 可以包含两个或更多 node:每个 node 绑定自己原生支持的平台;同一平台也可以有多个 node。
例如,下面的 builder 有 6 个节点:
| Builder 节点 | 原生 CPU | 只负责的平台 |
|---|---|---|
amd64-1、amd64-2、amd64-3 | x86_64 / AMD64 | linux/amd64 |
arm64-1、arm64-2、arm64-3 | ARM64 / AArch64 | linux/arm64 |
每台远程主机都需要 Docker Engine,本机则通过 SSH 连接其 Docker socket。endpoint 的格式是 ssh://<用户>@<主机>:<主机> 既可以是 DNS 名称,也可以是 IP 地址;SSH 非默认端口可在 URL 末尾添加端口。
# DNS 名称和 IP 地址都可以作为 SSH endpoint
docker context create amd-1 \
--docker "host=ssh://builder@amd64-builder.example.com"
docker context create arm-1 \
--docker "host=ssh://builder@192.168.1.12"
# root 技术上也可用,但生产环境建议使用专用的低权限 builder 用户
docker context create arm-2 \
--docker "host=ssh://root@192.168.1.13"
# SSH 使用非默认端口时,在主机地址后追加端口
docker context create amd-2 \
--docker "host=ssh://builder@192.168.1.14:2222"
# 其余节点按相同方式创建;名称必须与后续 buildx 命令一致
docker context create amd-3 --docker "host=ssh://builder@192.168.1.15"
docker context create arm-3 --docker "host=ssh://builder@192.168.1.16"
远程 SSH 用户必须有 Docker socket 访问权限;先在本机 ~/.ssh/config 或 SSH agent 中配置好密钥认证,不要在命令中传递密码。使用 root 时,SSH 私钥等同于远程主机的完全控制权,因此应优先创建专用 builder 用户,并限制其 SSH 登录方式和密钥用途。注意:能访问 Docker socket 的用户本身也接近 root 权限,不能把它当作普通低权限账号。
先确认每个 context 连到预期的架构:
docker --context amd-1 version
docker --context arm-1 version
创建第一个 node 后,对其余 context 重复使用 --append。以下以 3 个 AMD64 节点和 3 个 ARM64 节点为例:
# 第一个 node 创建 Buildx builder;它只接收 amd64 构建任务
docker buildx create \
--name native-multiarch \
--driver docker-container \
--node amd64-1 \
--platform linux/amd64 \
amd-1
# 追加其余 AMD64 节点
docker buildx create --append --name native-multiarch --node amd64-2 --platform linux/amd64 amd-2
docker buildx create --append --name native-multiarch --node amd64-3 --platform linux/amd64 amd-3
# 追加 ARM64 节点;每个 node 只接收 arm64 构建任务
docker buildx create --append --name native-multiarch --node arm64-1 --platform linux/arm64 arm-1
docker buildx create --append --name native-multiarch --node arm64-2 --platform linux/arm64 arm-2
docker buildx create --append --name native-multiarch --node arm64-3 --platform linux/arm64 arm-3
# 将该构建器设为当前终端默认值,并启动所有 BuildKit node
docker buildx use native-multiarch
docker buildx inspect --bootstrap
示例中的 amd-3、arm-3 context 需要按同样方式提前创建。docker buildx inspect --bootstrap 的输出应列出全部 node,并为它们显示 linux/amd64 或 linux/arm64。之后使用同一条构建命令即可,Buildx 会把每个平台的步骤调度到匹配架构的原生节点:
docker buildx build \
--builder native-multiarch \
--platform linux/amd64,linux/arm64 \
--tag registry.example.com/team/app:1.0.0 \
--push \
.
所有 node 都需要能拉取基础镜像、访问构建上下文中的远程依赖,并向目标镜像仓库推送。私有仓库凭据、企业代理、CA 证书和 DNS 配置应在所有节点保持一致。每个节点各自保存本地构建缓存;需要共享缓存时可使用后文的 registry cache。
同一架构追加多个 node 可以提高容量与故障切换能力,但不应把 Buildx 当作严格可控的任务负载均衡器。若需要精确控制并发、重试和构建分配,应由 CI 系统按架构拆分独立任务,再汇总和发布 manifest。
3. 准备一个可跨架构构建的 Dockerfile
绝大多数解释型应用可以直接使用支持多架构的官方基础镜像。下面以一个简单的 Node.js 应用为例,在空目录中创建 server.js:
const http = require('node:http');
const server = http.createServer((request, response) => {
response.writeHead(200, {'content-type': 'text/plain; charset=utf-8'});
response.end(`Hello from ${process.arch}\n`);
});
server.listen(3000, '0.0.0.0');
再创建 Dockerfile:
# syntax=docker/dockerfile:1
# 官方 Node 镜像包含 amd64 与 arm64 变体,Buildx 会选择匹配的平台
FROM node:22-alpine
WORKDIR /app
# 仅复制运行所需代码,避免把本地文件带入镜像
COPY server.js ./
# 使用非 root 用户运行 Node 服务
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Buildx 无法让只提供 amd64 版本的基础镜像自动变成 arm64。构建前可执行 docker buildx imagetools inspect <基础镜像>:<标签>,确认结果中包含目标平台。
4. 登录并一次构建、推送两个架构
多架构结果由多个镜像和一份 manifest list 组成,通常必须推送到仓库。--load 只能把单一平台镜像载入本地 Docker 镜像列表,因此多平台构建应使用 --push。
先登录目标仓库。以下以 Docker Hub 为例,请替换用户名:
# 使用访问令牌登录,不要把密码直接写进命令历史
printf '%s' "$DOCKERHUB_TOKEN" | \
docker login --username <Docker-Hub-用户名> --password-stdin
构建并推送版本标签和 latest 标签:
# 替换为自己的 Docker Hub 命名空间和镜像名
IMAGE=<Docker-Hub-用户名>/multiarch-demo
# 一次构建 amd64、arm64,推送每个架构镜像及总 manifest
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag "$IMAGE:1.0.0" \
--tag "$IMAGE:latest" \
--push \
.
私有仓库的镜像名应包含仓库域名,例如 registry.example.com/team/multiarch-demo:1.0.0。登录时也要指定该域名:
printf '%s' "$REGISTRY_PASSWORD" | \
docker login registry.example.com --username "$REGISTRY_USER" --password-stdin
构建完成后,日志末尾会显示 manifest 的 digest。记录该 digest,部署生产环境时可以用它锁定内容。
5. 验证已发布的架构
不要只看仓库页面显示“已推送”。使用 imagetools inspect 检查 manifest:
# 查看远程镜像清单;应看到 linux/amd64 和 linux/arm64 两个条目
docker buildx imagetools inspect "$IMAGE:1.0.0"
输出中应包含类似内容:
Manifests:
Name: docker.io/<用户名>/multiarch-demo:1.0.0@sha256:...
Platform: linux/amd64
Name: docker.io/<用户名>/multiarch-demo:1.0.0@sha256:...
Platform: linux/arm64
也可以在当前机器指定拉取平台进行验证:
# 拉取并运行与当前主机相同架构的版本
docker run --rm --publish 3000:3000 "$IMAGE:1.0.0"
# 另开一个终端请求服务;输出会显示 node 进程实际架构
curl http://127.0.0.1:3000
在 amd64 主机上强制运行 arm64 镜像会依赖模拟,适合功能验证,不适合性能测试:
# 仅在 Docker Desktop 或已配置 QEMU 的机器上测试
docker run --rm \
--platform linux/arm64 \
"$IMAGE:1.0.0" \
node -p 'process.arch'
6. 本地调试单一平台
发布之前,先在本机架构上检查 Dockerfile 是更快的做法。使用 --load 把构建结果加载到本地:
# 只构建当前机器的 amd64 版本并加载到本地镜像列表
docker buildx build \
--platform linux/amd64 \
--tag multiarch-demo:local \
--load \
.
# 确认镜像架构与运行命令
docker image inspect multiarch-demo:local \
--format 'architecture={{.Architecture}} os={{.Os}}'
docker run --rm multiarch-demo:local node -p 'process.arch'
如果本机是 Apple silicon,请把 linux/amd64 改为 linux/arm64,或者省略 --platform 让 Buildx 使用默认平台。
7. 构建缓存与 CI
多架构构建会分别处理每个目标平台,缓存对于 CI 很重要。以下示例使用镜像仓库存储缓存,适合多数 CI 平台:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag "$IMAGE:1.0.1" \
--cache-from type=registry,ref="$IMAGE:buildcache" \
--cache-to type=registry,ref="$IMAGE:buildcache",mode=max \
--push \
.
缓存仓库应与生产镜像使用相同的访问控制。不要把包含密钥、私有依赖或敏感构建产物的层写入共享缓存;更根本的做法是在 Dockerfile 中使用 BuildKit secret mount,而不是通过 ARG 或 ENV 传入密钥。
GitHub Actions 可使用 docker/setup-buildx-action 配置构建器,再通过 docker/build-push-action 将平台、标签和缓存作为参数传入。无论使用哪种 CI,发布前都应运行测试、使用不可变版本标签,并在发布后检查 manifest。
8. 常见问题
multiple platforms feature is currently not supported for docker driver
当前使用的是默认 docker 驱动。创建并切换到 docker-container 构建器:
docker buildx create --name multiarch --driver docker-container --use
docker buildx inspect --bootstrap
no match for platform in manifest
通常是基础镜像不支持目标架构,或标签写错。检查基础镜像的 manifest:
docker buildx imagetools inspect <基础镜像>:<标签>
改用同时提供 linux/amd64 和 linux/arm64 的官方镜像标签,或为不支持的平台准备独立 Dockerfile。
构建后本地看不到镜像
多平台构建搭配 --push 时,结果已经推到仓库,并不会进入本地 docker image ls。本地调试请只选择一个平台并添加 --load;需要导出 OCI 镜像文件可使用 --output type=oci,dest=image.tar。
非本机架构构建很慢或失败
QEMU 模拟对编译密集型任务较慢,也可能遇到指令集兼容问题。优先使用原生 arm64 和 amd64 CI Runner,或为 Buildx 配置远程原生节点。不要把模拟构建的耗时直接当作生产运行性能。
9. 发布前检查清单
docker buildx inspect --bootstrap显示所有目标平台;- 基础镜像的 manifest 包含目标平台;
- Dockerfile 不复制密钥、
.env或本地构建产物; - 已使用不可变版本标签,例如
1.0.0或 Git 提交短 SHA; - 构建命令使用
--platform ... --push,而不是多平台配合--load; docker buildx imagetools inspect <镜像>:<标签>显示每个目标平台;- 在至少一种目标架构的真实环境中完成运行验证。