Docker Buildx 多架构构建:一条命令打出 x86 和 ARM 通用镜像

概述

你有没有遇到过这种场景:在本地 amd64 机器上用 Docker 跑得好好的项目,部署到 ARM 服务器(比如树莓派、AWS Graviton 实例)上直接报错 “exec format error”?

问题出在镜像架构上。普通的 docker build 只能构建当前机器的架构镜像,想支持多架构就得在每台机器上分别 build,或者手动用 qemu 模拟,又慢又麻烦。

Docker Buildx 是 Docker 官方的构建增强工具,基于 BuildKit,支持跨平台交叉编译和多架构镜像构建。一条命令,同时打出 amd64 和 arm64 的镜像,推送后任意架构都能直接用。

这篇教程带你从零配置 Buildx,写一个支持多架构的 Dockerfile,最后把镜像推送到 Docker Hub。

准备工作

你需要以下条件:

  • 一台 Linux 服务器或本地电脑(Ubuntu 20.04+、Debian 11+ 均可)
  • Docker 已安装(版本 23.0+ 自带 buildx 插件)
  • Docker Hub 账号(或任意容器镜像仓库)
  • 约 2GB 磁盘空间用于构建缓存

检查 Buildx 是否可用:

docker buildx version

如果输出版本号,说明已就绪。如果报 docker: "buildx" is not a docker command,先升级 Docker。

步骤

第一步:创建 Buildx 构建器实例

默认情况下,Docker 只有一个内置构建器,只支持本机架构。我们需要创建一个支持多平台的目标构建器。

docker buildx create --name multiarch-builder --driver docker-container --use
docker buildx inspect --bootstrap

第二条命令会启动一个容器化的 BuildKit 构建环境,并验证其支持的平台列表。输出中应该看到 linux/amd64 和 linux/arm64(可能还有 linux/arm/v7)。

第二步:准备一个多架构项目

随便写个最简单的 Go 程序(Go 交叉编译支持极好,适合作为演示)。创建项目目录:

mkdir -p ~/multiarch-demo && cd ~/multiarch-demo
cat > main.go << 'EOF'
package main

import (
    "fmt"
    "runtime"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello from %s/%s!", runtime.GOOS, runtime.GOARCH)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}
EOF

创建一个多阶段构建的 Dockerfile,利用 BuildKit 的交叉编译能力:

cat > Dockerfile << 'EOF'
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder

WORKDIR /app
COPY . .

# Go 原生支持交叉编译,只需要设置交叉编译参数
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server . && \
    CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=0 go build -o server-arm64 .

FROM --platform=$TARGETPLATFORM alpine:3.19

RUN apk add --no-cache tini
WORKDIR /app
COPY --from=builder /app/server* ./

# 根据目标架构选择正确的二进制文件
RUN if [ "$TARGETARCH" = "arm64" ]; then \
        mv server-arm64 server; \
    fi

EXPOSE 8080
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["./server"]
EOF

这段 Dockerfile 用 --platform=$BUILDPLATFORM 在构建机上编译两种架构的二进制,再用 --platform=$TARGETPLATFORM 在目标机上选择对应的二进制。关键是 $BUILDPLATFORM 和 $TARGETPLATFORM 是 BuildKit 自动注入的变量。

第三步:构建并推送多架构镜像

用 --push 参数一次性构建并推送所有平台镜像:

docker buildx build \
    --platform linux/amd64,linux/arm64 \
    --tag yourname/multiarch-demo:latest \
    --push \
    .

你会看到输出类似:

#1 [internal] load build definition from Dockerfile
#2 [internal] load metadata for docker.io/library/golang:1.22-alpine
#3 [linux/amd64 1/3] FROM docker.io/library/golang:1.22-alpine
#4 [linux/arm64 1/3] FROM docker.io/library/golang:1.22-alpine@sha256:...
# ...
#7 DONE 45.2s

#8 [push] writing image sha256:abc123...
#9 [push] writing image sha256:def456...
#10 [push] exporting manifest list sha256:789xyz...
#11 [push] success!

注意最后的 exporting manifest list——这就是多架构镜像的" Manifest List",它记录了不同架构镜像的索引。Docker 拉取时会根据客户端架构自动选择正确的镜像。

验证推送结果:

docker buildx imagetools inspect yourname/multiarch-demo:latest

输出应该列出 linux/amd64 和 linux/arm64 两个平台。

第四步:在不同架构机器上验证

现在你在任何架构的机器上都可以直接拉取运行:

# 在 amd64 机器上
docker run --rm yourname/multiarch-demo:latest
# 输出: Hello from linux/amd64!

# 在 arm64 机器上(比如树莓派)
docker run --rm yourname/multiarch-demo:latest
# 输出: Hello from linux/arm64!

不需要重新构建,同一个镜像 tag 在任何架构上都能跑。

验证

检查镜像是否正确构建了多架构支持:

# 方式1:用 imagetools 查看
docker buildx imagetools inspect yourname/multiarch-demo:latest

# 方式2:用 docker manifest inspect(需要 Docker 20.10+)
docker manifest inspect yourname/multiarch-demo:latest

输出中应该能看到 two platform entries:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    }
  ]
}

看到 manifests 里有两个平台条目,说明多架构构建成功。

常见问题

Q: Buildx 构建很慢,尤其是 arm64 部分

A: 正常现象。没有硬件的话,BuildKit 会通过 qemu 模拟器交叉编译 arm64,速度比原生慢 3-5 倍。两个办法缓解:

# 1. 用 BuildKit 远程构建(在你的 amd64 机器上远程调用)
docker buildx create --name remote-builder --driver docker-container
docker buildx build --builder remote-builder ...

# 2. 开启缓存,避免每次重新编译
docker buildx build --cache-from type=local,src=/tmp/buildcache --cache-to type=local,dest=/tmp/buildcache-new ...

Q: 我的项目有 C 扩展(需要 CGO),交叉编译失败了

A: CGO 交叉编译比纯 Go 复杂。可以用 --build-arg 传入交叉编译工具链,或者用 Docker 官方提供的多平台基础镜像:

# 用支持多平台的 base image
FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
# ...
FROM --platform=$TARGETPLATFORM debian:bookworm-slim

或者考虑用 crosstool-NG 预先准备好交叉编译工具链。

Q: 推送到 GitHub Container Registry(GHCR)可以吗

A: 完全可以,命令一样,只是改一下 tag:

docker buildx build \
    --platform linux/amd64,linux/arm64 \
    --tag ghcr.io/youruser/yourapp:latest \
    --push \
    .

注意 GHCR 需要登录:echo $GH_TOKEN | docker login ghcr.io -u youruser --password-stdin

Q: 构建出来的镜像体积怎么这么大

A: 因为同时包含了两个架构的二进制。解决方法是用多阶段构建,每个平台只 copy 对应的二进制:

FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETARCH
RUN GOOS=linux GOARCH=$TARGETARCH go build -o server .
FROM --platform=$TARGETPLATFORM alpine:3.19
COPY --from=builder /app/server ./

这样每个架构镜像只包含自己的二进制,体积更小。

Q: 能支持 Linux ARM v7(32位)吗

A: 可以,加 linux/arm/v7 就行:

docker buildx build \
    --platform linux/amd64,linux/arm64,linux/arm/v7 \
    --tag yourname/multiarch-demo:latest \
    --push \
    .

注意 ARM v7 需要指定 GOARM=7:CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build

Q: 在 GitHub Actions 里怎么自动多架构构建

A: 用 docker/setup-buildx-action,CI 里一行搞定:

- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKER_USER }}
    password: ${{ secrets.DOCKER_PASSWORD }}
- run: |
    docker buildx build \
      --platform linux/amd64,linux/arm64 \
      --push -t yourname/app:latest .

下一步

Buildx 除了多架构构建,还支持 BuildKit 缓存、本地 Dockerfile 缓存、远程缓存后端等功能。掌握了多架构构建,你的镜像就能在任何平台上运行了。

你现在的 Docker 项目,有在 ARM 设备上跑的需求吗?

这篇文章对你有帮助吗?