跳到主要内容

Buildx 多架构镜像构建

同一份应用镜像可能需要运行在不同 CPU 上:常见的云服务器多为 linux/amd64,Apple silicon 开发机、树莓派和部分云实例则使用 linux/arm64。如果只构建一种架构,另一种机器拉取镜像时可能报 no matching manifest for linux/...,或只能通过性能较差的模拟运行。

Docker Buildx 可以在一次构建中生成多个架构的镜像,并将它们以**多架构清单(manifest list)**推送到镜像仓库。用户仍然只需拉取同一个镜像标签,Docker 会自动选择与当前机器匹配的镜像。

完成本篇后,你将能够:

  • 确认 Buildx、BuildKit 与目标架构是否可用;
  • 创建支持多架构的构建器;
  • amd64arm64 原生节点组合为一个构建器;
  • 一次构建并推送 linux/amd64linux/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/amd64linux/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/amd64linux/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-1amd64-2amd64-3x86_64 / AMD64linux/amd64
arm64-1arm64-2arm64-3ARM64 / AArch64linux/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-3arm-3 context 需要按同样方式提前创建。docker buildx inspect --bootstrap 的输出应列出全部 node,并为它们显示 linux/amd64linux/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,而不是通过 ARGENV 传入密钥。

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/amd64linux/arm64 的官方镜像标签,或为不支持的平台准备独立 Dockerfile。

构建后本地看不到镜像

多平台构建搭配 --push 时,结果已经推到仓库,并不会进入本地 docker image ls。本地调试请只选择一个平台并添加 --load;需要导出 OCI 镜像文件可使用 --output type=oci,dest=image.tar

非本机架构构建很慢或失败

QEMU 模拟对编译密集型任务较慢,也可能遇到指令集兼容问题。优先使用原生 arm64amd64 CI Runner,或为 Buildx 配置远程原生节点。不要把模拟构建的耗时直接当作生产运行性能。

9. 发布前检查清单

  1. docker buildx inspect --bootstrap 显示所有目标平台;
  2. 基础镜像的 manifest 包含目标平台;
  3. Dockerfile 不复制密钥、.env 或本地构建产物;
  4. 已使用不可变版本标签,例如 1.0.0 或 Git 提交短 SHA;
  5. 构建命令使用 --platform ... --push,而不是多平台配合 --load
  6. docker buildx imagetools inspect <镜像>:<标签> 显示每个目标平台;
  7. 在至少一种目标架构的真实环境中完成运行验证。