Docker 命令 之 Dockerfile 多平台构建

摘要

什么是Dockerfile 多平台构建?

  • Dockerfile 多平台构建,可以构建多个平台镜像,比如 arm64、amd64、arm、386 等。

  • 在没有安装 docker-buildx-plugin 的情况下,docker build 命令是不支持使用 --platform 构建出跨平台镜像的,其仅能构建与本机架构平台相同的镜像。

  • 要真正实现跨平台构建(multi-platform build),比如在 amd64 上构建 arm64 的镜像,需要使用 BuildKit 和 buildx 插件。

  • docker-buildx-plugin 是基于 BuildKit 构建的,但它本身是 Buildx 的一个实现形式,它扩展了 docker build 的能力,支持多平台构建(如同时构建 Linux/amd64 和 Linux/arm64等)。

安装 docker-buildx-plugin

  • 随docker服务一起安装

1
sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  • 单独安装

1
sudo dnf -y install docker-buildx-plugin
  • 查看版本

1
docker buildx version

docker buildx 基本命令

  • 以下命令在后面的示例中都有使用

命令 说明
docker buildx create 创建一个新的构建器实例
docker buildx use 设置当前使用的构建器
docker buildx inspect 查看构建器状态和支持的平台
docker buildx build 构建镜像(增强版),等同于 docker build
docker buildx ls 列出所有构建器
docker buildx rm 删除构建器
docker buildx du 查看构建器使用的磁盘空间
docker buildx prune 删除构建过程中产生的缓存
docker buildx version 查看 Docker Buildx 的版本信息

要让Docker支持多平台构建,需要满足以下几个条件:

  • Linux内核开启多处理器架构支持

  • 构建时使用基于docker-container驱动的Buildx实例

  • 使用docker buildx build命令构建镜像,构建命令必须指定–platform参数

Linux内核开启多处理器架构支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 列出所有构建器,默认情况下只有一个名称为"default"的构建器
docker buildx ls
## 输出
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default docker
\_ default \_ default running v0.13.2 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386

# 开启多处理器架构支持
docker run --privileged --rm tonistiigi/binfmt --install all
# 这个镜像用完就可以删除了
docker rmi tonistiigi/binfmt

# 再次列出所有构建器
docker buildx ls
## 输出,此时可以看到PLATFORMS中有多个平台
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default* docker
\_ default \_ default running v0.13.2 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/mips64le, linux/mips64, linux/loong64, linux/arm/v7, linux/arm/v6

构建时使用基于docker-container驱动的Buildx实例

  • Docker 的默认构建驱动是 docker,它是运行在本地 Docker 守护进程上的,不能进行真正的多平台构建(仅能构建当前平台)。

  • 多平台构建需要使用 BuildKit 的 container 驱动,它以容器的形式运行构建器,支持虚拟化平台并行构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看当前默认的构建器default
docker buildx inspect default
# 输出
Name: default
Driver: docker
Last Activity: 2025-06-06 07:42:42 +0000 UTC

Nodes:
Name: default
Endpoint: default
Status: running
BuildKit version: v0.13.2
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/mips64le, linux/mips64, linux/loong64, linux/arm/v7, linux/arm/v6
Labels:
org.mobyproject.buildkit.worker.moby.host-gateway-ip: 172.17.0.1
  • 创建基于 docker-container 驱动的Buildx实例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建并切换到名为mybuilder的构建器实例
docker buildx create --name mybuilder --use --driver docker-container
# 查看当前构建器信息,--bootstrap:查看前确保构建器已启动 ,此时看到其Driver为docker-container,这个命令第一次执行时可能会提示错误,不过不用管,再次运行就正常了
docker buildx inspect --bootstrap
## 输出
Name: mybuilder
Driver: docker-container
……………………………………

# 此时会启动一个名称为`buildx_buildkit_mybuilder0`的容器,每创建一个构建器实例,就会启动一个名称为`buildx_buildkit_xxx`的容器,删除构建器时,其对应的容器也会被删除
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0ab9cab9ec9b moby/buildkit:buildx-stable-1 "buildkitd --allow-i…" About an hour ago Up About an hour buildx_buildkit_mybuilder0
  • buildx_buildkit_xxx这个容器就是一个 BuildKit 守护进程容器,它是执行 buildx 构建任务的实际工作引擎。其对应的镜像为moby/buildkit:buildx-stable-1,容器会在执行docker buildx builddocker buildx inspect --bootstrap 时启动

功能 说明
👷 执行构建任务 真正执行 buildx build 指令里的构建过程,比如多阶段构建、缓存处理、跨平台编译等
📦 拉取镜像 拉取 Dockerfile 中的基础镜像
📤 上传/导出镜像 支持导出为 docker image, tar, 推送到远程 registry
🪣 管理缓存 管理构建缓存(中间镜像、层等)以加速后续构建
🌍 支持多平台 通过 QEMU 或交叉编译器支持跨平台(如构建 arm64 镜像)
  • 在国内环境使用时,如果不能科学上网,当我们在构建镜像时会提示无法拉取基础镜像,即便我们已经在/etc/docker/daemon.json中配置了国内的镜像源也不行,这是因为docker buildx 使用的是 BuildKit,它的行为不同于传统的 docker build

特性 docker build(传统) docker buildx(BuildKit)
是否使用本地镜像缓存 ✅ 是 ⚠️ 不是
是否需要联网拉取元数据(即使镜像已存在)
构建网络隔离 不隔离 隔离构建,无法直接访问宿主镜像缓存
  • 解决方法为构建器配置镜像源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建 BuildKit 配置文件 buildkitd.toml
cat <<EOF > buildkitd.toml
[registry."docker.io"]
mirrors = ["https://docker.1ms.run"]

[worker.oci]
gc = true
EOF

# 创建 BuildKit 构建器,并指定配置文件
docker buildx create --name mybuilder --use --driver docker-container --config ./buildkitd.toml
docker buildx inspect --bootstrap

# 如果存在同名的构建器,则删除它后再创建
docker buildx rm mybuilder
docker buildx create --name mybuilder --use --driver docker-container --config ./buildkitd.toml
docker buildx inspect --bootstrap

# 查看构建器列表
docker buildx ls
## 输出
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
mybuilder* docker-container
\_ mybuilder0 \_ unix:///var/run/docker.sock running v0.21.1 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/loong64, linux/arm/v7, linux/arm/v6
default docker
\_ default \_ default running v0.13.2 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/mips64le, linux/mips64, linux/loong64, linux/arm/v7, linux/arm/v6

# 如果要切换回默认的构建器,请执行以下命令
docker buildx use default

使用docker buildx build命令构建镜像,构建命令必须指定--platform参数

示例一:单阶段构建

  • 我们依旧以一个springboot项目为例,其Dockerfile如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 使用轻量级 Alpine 版本的 OpenJDK 17 官方镜像,适合部署 Spring Boot 应用
FROM openjdk:17-alpine

# 设置构建时变量,默认使用构建好的 jar 文件
ARG JAR_FILE=app.jar

# 设置运行时环境变量
ENV JAVA_OPTS="-Xms1024M -Xmx1024M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M"
ENV TZ=Asia/Shanghai

# 镜像元信息
LABEL maintainer="yourname@example.com"
LABEL version="1.0.0"
LABEL description="用于部署 Spring Boot 应用的生产级镜像"

# 设置工作目录
WORKDIR /app

# 复制 Spring Boot 构建生成的 jar 包
COPY ${JAR_FILE} app.jar

# 声明暴露的应用端口(Spring Boot 默认是 8080)
EXPOSE 8080

# 设置容器启动时的主命令(ENTRYPOINT 不会被 docker run 参数覆盖)
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

# CMD 提供默认的运行参数,可以被 docker run 覆盖
# 这里通过 Spring Boot 参数设置启动环境和端口号
CMD ["--spring.profiles.active=app", "--server.port=8080"]
# 这里使用的是 ENTRYPOINT + CMD 的混合模式
# 完整命令: java $JAVA_OPTS -jar app.jar --spring.profiles.active=app --server.port=8080

# 声明一个卷挂载点,运行容器时可将该路径映射到宿主机,实现数据持久化
VOLUME ["/app/logs"]
  • 开始构建镜像,docker buildx build == docker build,构建多平台架构时需要使用--platform指定构建平台

1
2
3
4
5
6
7
8
9
# --platform inux/arm64 :构建arm64镜像
# -t "app.arm64" :构建后镜像名称,没有指定版本默认就是:latest
# --load :构建后加载到本地
# . :当前目录查找Dockerfile文件
# -f :如果名称不为Dockerfile,则通过该参数指定Dockerfile文件的名称
docker buildx build --platform linux/arm64 -t "app.arm64" --load .
## 此时会遇到如下错误
ERROR: failed to solve: openjdk:17-alpine: failed to resolve source metadata for docker.io/library/openjdk:17-alpine: no match for platform in manifest: not found
## 原因:openjdk:17-alpine 这个镜像不支持arm64平台,我们需要更换一个支持多平台的镜像
  • 如何查看镜像是否支持多平台呢?可以在docker hub上查看,也可以使用如下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 查询镜像支持的平台,不过国内依旧是不支持
docker buildx imagetools inspect nginx:latest | grep Platform | sort | uniq
# 可以在镜像名称前加上国内的镜像仓库地址进行查询,比如:
docker buildx imagetools inspect docker.1ms.run/nginx:latest | grep Platform | sort | uniq
## 输出结果
Platform: linux/386
Platform: linux/amd64
Platform: linux/arm64/v8
Platform: linux/arm/v5
Platform: linux/arm/v7
Platform: linux/mips64le
Platform: linux/ppc64le
Platform: linux/s390x
Platform: unknown/unknown

# 接着我们查看openjdk:17-alpine这个镜像
docker buildx imagetools inspect docker.1ms.run/openjdk:17-alpine | grep Platform | sort | uniq
## 输出结果,其确实不支持 linux/arm64
Platform: linux/amd64

# 我们换一个openjdk镜像试试
docker buildx imagetools inspect docker.1ms.run/openjdk:17-slim | grep Platform | sort | uniq
## 输出结果,可以看到这个镜像同时支持amd64和arm64架构
Platform: linux/amd64
Platform: linux/arm64/v8


# 也可以使用skopeo容器的方式进行查询
docker run --rm quay.io/skopeo/stable:latest inspect --raw --override-os linux docker://docker.1ms.run/openjdk:17-slim | jq -r '.manifests[].platform | "\(.os)/\(.architecture)/\(.variant)"' | sort | uniq | sed 's/\/null//'
## 输出结果
linux/amd64
linux/arm64/v8
  • 替换支持多架构的镜像后重新构建镜像就会成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
docker buildx build --platform linux/arm64 -t "app.arm64" --load .
docker buildx build --platform linux/amd64 -t "app.amd64" --load .
# 列出镜像
docker images
## 输出结果
REPOSITORY TAG IMAGE ID CREATED SIZE
app.amd64 latest 859162438372 2 hours ago 431MB
app.arm64 latest 0058c70dd16e 2 hours ago 426MB

# 查看构建后镜像的架构
docker inspect app.amd64 | jq '.[0].Architecture'
## 输出
"amd64"

# 我们也可以将构建后的镜像输出到本地目录
docker buildx build --platform linux/amd64 -t "app.amd64" --output type=docker,dest=./app.amd64.tar .
# 然后再将镜像导入到本地镜像仓库中
docker load -i app.amd64.tar
  • --load--output都不支持一个镜像多种架构,要构建像openjdk:17-slim这种支持多架构的镜像可以使用--push,一步就推送到远程仓库

1
2
3
4
5
6
# 构建多架构镜像,--push 可以一步做到:多平台构建的镜像不能只保存到本地,必须推送到远程 registry 才能合并架构。
# 这里以推送到 docker hub 为例
# 登录 docker hup
docker login -u hanqunfeng
# 构建,注意这里的镜像名称要加上你的dockerhub的命名空间,--platform 指定构建的架构,可以指定多个,需要Dockerfile配置基础镜像支持对应的架构
docker buildx build --platform linux/amd64,linux/arm64 -t hanqunfeng/app:latest --push .

  • 镜像拉取

1
2
# 拉取指定架构镜像,不指定 --platform 参数,默认拉取当前架构的镜像
docker pull --platform=linux/arm64 hanqunfeng/app:latest

示例二:多阶段构建

  • 要求每个阶段中的基础镜像都要支持多架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 第一阶段:获取代码
FROM alpine/git AS fetcher
WORKDIR /workspace/application
# 将替换为实际的Git仓库URL和分支/标签
ARG GIT_REPOSITORY=https://gitee.com/hanqunfeng/springbootweb.git
ARG GIT_BRANCH=master
RUN git clone -b ${GIT_BRANCH} ${GIT_REPOSITORY} .

# 第二阶段:使用Maven环境进行构建
FROM maven:3.8.4-openjdk-17 AS builder
WORKDIR /workspace/application
# 从第一阶段复制代码
COPY --from=fetcher /workspace/application .
# 使用Maven清理并打包
RUN mvn clean package -DskipTests

# 第三阶段:创建最终的运行环境
FROM openjdk:17-slim
WORKDIR /app
# 设置运行时环境变量
ENV JAVA_OPTS="-Xms1024M -Xmx1024M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M"
# 将第二阶段生成的目标文件复制到这里。注意这里假设你的spring boot工程打成的jar名是target/app.jar
COPY --from=builder /workspace/application/target/app.jar app.jar
# 暴露端口(如果需要的话)。请根据实际情况修改端口号
EXPOSE 8080
# 声明一个卷挂载点,运行容器时可将该路径映射到宿主机,实现数据持久化
VOLUME ["/app/logs"]
# 设置容器启动时的主命令(ENTRYPOINT 不会被 docker run 参数覆盖)
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# CMD 提供默认的运行参数,可以被 docker run 覆盖
# 这里通过 Spring Boot 参数设置启动环境和端口号
CMD ["--spring.profiles.active=app", "--server.port=8080"]
# 这里使用的是 ENTRYPOINT + CMD 的混合模式
# 完整命令: java $JAVA_OPTS -jar app.jar --spring.profiles.active=app --server.port=8080
  • 构建镜像

1
2
# 构建多平台镜像并发布到dockerhub
docker buildx build --platform linux/amd64,linux/arm64 -t hanqunfeng/springboot:latest --push .

如何将构建好的多个单平台镜像发布为一个多平台镜像

  • 如果你已经分别构建好单平台镜像,也可以用 docker buildx imagetools create 来合并

  • 以上面创建的两个单平台镜像为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 列出镜像
docker images
## 输出结果
REPOSITORY TAG IMAGE ID CREATED SIZE
app.amd64 latest 859162438372 2 hours ago 431MB
app.arm64 latest 0058c70dd16e 2 hours ago 426MB

# 一定要推送单平台镜像到远程仓库,否则无法完成合并
docker tag app.amd64 hanqunfeng/app:amd64
docker push hanqunfeng/app:amd64

docker tag app.arm64 hanqunfeng/app:arm64
docker push hanqunfeng/app:arm64

# 合并,创建多平台镜像指向(manifest list)
docker buildx imagetools create \
--tag hanqunfeng/app:latest \
hanqunfeng/app:amd64 \
hanqunfeng/app:arm64

  • 如果不需要单平台镜像,可以在dockerhub上删除即可。

移除 BuildKit(Buildx)构建过程中产生的缓存数据

  • BuildKit(Buildx)构建过程会产生缓存数据,包括未使用的中间镜像、构建层等。对于频繁使用 Docker 构建的开发者来说,这些缓存会逐渐占用大量磁盘空间。

  • 可以通过该命令查看缓存占用磁盘空间的大小

1
docker buildx du
  • docker buildx prune 是一个用于 清理 Docker Buildx 构建缓存 的命令,常用于释放磁盘空间。

  • 命令语法

1
docker buildx prune [OPTIONS]
OPTIONS 作用
-a, --all 包括内部/前端镜像。默认只删除无用缓存,加上该参数会清除更多缓存内容,包括可能仍可用的内容(更彻底)。
--builder string 指定使用哪个 builder 实例(可通过 docker buildx ls 查看当前有哪些 builder)。
--filter filter 设定清理条件,例如:until=24h 表示只删除 24 小时前的缓存。
-f, --force 不提示确认,直接执行清理操作。常用于脚本中。
--keep-storage bytes 保留指定大小的缓存空间,其余删除(如:--keep-storage 5GB)。
--verbose 输出更详细的清理信息。
  • 示例

1
2
3
4
5
# 删除所有未使用的缓存,并跳过确认提示
docker buildx prune -f

# 只删除 24 小时前的缓存,保留 10GB 的缓存空间
docker buildx prune --filter "until=24h" --keep-storage 10GB