跳到主要内容

Docker Manifest 发布多架构镜像

有些团队不在一台机器上交叉构建所有架构,而是让 AMD64 与 ARM64 Runner 分别构建、测试并推送镜像。此时,仓库中已有两个架构专用标签,例如 app:1.2.0-amd64 与 app:1.2.0-arm64,但使用者不应该记住 CPU 架构再选择标签。

Docker Manifest 可以把这些已存在的镜像汇总为一个统一标签,例如 app:1.2.0。之后无论在 linux/amd64 还是 linux/arm64 主机执行相同的 docker pull app:1.2.0,Docker 都会根据本机平台自动选择对应镜像。

本篇关注汇总和发布,不重复介绍 Buildx 的一次性多架构构建。若镜像尚未构建,请先阅读 Buildx 多架构镜像构建。

完成本篇后,你将能够:

  • 设计可追溯的架构专用镜像标签;
  • 用 docker manifest create 将多个平台镜像组成统一版本;
  • 使用 annotate 明确声明平台元数据;
  • 推送 Manifest 并验证仓库中的平台列表;
  • 在 CI 中避免“部分架构发布成功”的不完整版本。

1. Manifest 与镜像标签的关系​

一个普通镜像标签通常指向单个平台的 manifest,例如:

registry.example.com/team/api:1.2.0-amd64
-> linux/amd64 的镜像配置和层

多架构版本标签则指向一个镜像索引(也常被称为 manifest list),索引再指向各平台镜像:

registry.example.com/team/api:1.2.0
-> linux/amd64 -> api:1.2.0-amd64
-> linux/arm64 -> api:1.2.0-arm64

统一标签不复制镜像层,也不会重新构建镜像;它只保存对各平台 manifest 的引用。因此,先推送所有架构专用镜像,再创建并推送统一标签,是最稳妥的发布顺序。

术语说明

OCI Image Index 与 Docker Manifest List 的作用相同:都是将多个平台镜像放在同一个可解析的引用下。不同仓库页面可能使用不同名称,Docker 客户端会自动处理兼容格式。

2. 发布前准备​

2.1 规划标签​

建议使用不可变版本号加架构后缀作为源标签,再用无后缀版本作为用户拉取的统一标签:

用途标签示例谁使用
AMD64 源镜像api:1.2.0-amd64AMD64 Runner、排障和发布任务
ARM64 源镜像api:1.2.0-arm64ARM64 Runner、排障和发布任务
多架构版本api:1.2.0生产部署、使用者
多架构滚动标签api:stable仅在版本发布验证后更新

不要把 latest-amd64、latest-arm64 作为唯一发布依据。滚动标签会被后续构建覆盖,无法证明某个统一标签实际引用了哪一版镜像。

2.2 登录仓库并定义变量​

以下示例使用私有仓库;Docker Hub 只需将镜像名改为 <用户名>/api。访问令牌通过环境变量传入,避免写进终端历史。

# 版本标签应来自发布版本或 Git 提交,不要在生产发布中只使用 latest
export IMAGE=registry.example.com/platform/api
export VERSION=1.2.0

# 使用最小权限的机器人账号登录目标仓库
printf '%s' "$REGISTRY_PASSWORD" | \
docker login registry.example.com \
--username "$REGISTRY_USER" \
--password-stdin

执行发布的 Docker CLI 必须能访问镜像仓库;各架构镜像也必须已经推送到同一个仓库路径。docker manifest 操作的是远程引用,不能把只存在本地 docker image ls 中的镜像直接汇总为跨机器可用的 manifest。

2.3 确认架构专用镜像已经存在​

先检查每个源标签。imagetools inspect 直接读取远程仓库,适合发布前的自动检查:

# 应分别返回 linux/amd64 和 linux/arm64
docker buildx imagetools inspect "$IMAGE:$VERSION-amd64"
docker buildx imagetools inspect "$IMAGE:$VERSION-arm64"

如果团队使用原生 Runner 构建,两个 CI 任务通常分别执行类似命令:

# AMD64 Runner:构建、测试后推送 AMD64 源标签
docker buildx build \
--platform linux/amd64 \
--tag "$IMAGE:$VERSION-amd64" \
--push \
.
# ARM64 Runner:在 ARM64 主机上构建、测试后推送 ARM64 源标签
docker buildx build \
--platform linux/arm64 \
--tag "$IMAGE:$VERSION-arm64" \
--push \
.

docker buildx build --push 需要 Buildx 与 BuildKit。重点是每个源标签都已成功推送,而非由哪一个 CI 产品执行构建。

源镜像必须真的是目标架构

标签名带 -arm64 并不能让镜像自动变成 ARM64。请用 docker buildx imagetools inspect 或在相应架构机器上运行镜像确认平台。错误的架构元数据会导致使用者拉取到无法执行的镜像。

3. 创建多架构 Manifest​

3.1 创建统一版本标签​

docker manifest create 只在本机 CLI 中创建待推送的 manifest list,不会立即改变远程仓库。将两个已经存在的源标签加入统一版本:

# 创建本地 manifest list;顺序不影响 Docker 选择平台的结果
docker manifest create "$IMAGE:$VERSION" \
"$IMAGE:$VERSION-amd64" \
"$IMAGE:$VERSION-arm64"

如需在发布脚本中重复执行,可先清理同名的本地 manifest 缓存。该命令不会删除远程镜像或远程标签:

# 仅删除本机 Docker CLI 保存的 manifest 定义;不存在时忽略错误
docker manifest rm "$IMAGE:$VERSION" 2>/dev/null || true

# 再次创建待发布的 manifest list
docker manifest create "$IMAGE:$VERSION" \
"$IMAGE:$VERSION-amd64" \
"$IMAGE:$VERSION-arm64"

3.2 标注平台元数据​

当源镜像 manifest 已经正确包含操作系统和 CPU 架构时,Docker 通常能自动识别。生产发布仍建议显式标注,尤其是源镜像来自不同构建系统或私有仓库迁移后:

# 明确标注 AMD64 镜像的平台信息
docker manifest annotate "$IMAGE:$VERSION" \
"$IMAGE:$VERSION-amd64" \
--os linux \
--arch amd64
# 明确标注 ARM64 镜像的平台信息
docker manifest annotate "$IMAGE:$VERSION" \
"$IMAGE:$VERSION-arm64" \
--os linux \
--arch arm64

如需加入 ARM v7、Windows 或其他平台,应额外创建对应源镜像,并在同一份 manifest 中标注完整平台。不要把 Linux 与 Windows 运行时兼容性当作理所当然:它们需要不同的基础镜像、节点系统和部署策略。

3.3 推送到仓库​

确认本地定义后再推送统一标签:

# 推送 manifest list;--purge 在成功后清理本地临时定义,不会删除远程源镜像
docker manifest push --purge "$IMAGE:$VERSION"

如果需要同步发布滚动标签,必须重新创建一份 manifest,因为标签是独立引用:

# stable 只在 1.2.0 已完成验证后更新
docker manifest create "$IMAGE:stable" \
"$IMAGE:$VERSION-amd64" \
"$IMAGE:$VERSION-arm64"
# 为 stable 标注两个平台并推送
docker manifest annotate "$IMAGE:stable" "$IMAGE:$VERSION-amd64" --os linux --arch amd64
docker manifest annotate "$IMAGE:stable" "$IMAGE:$VERSION-arm64" --os linux --arch arm64
docker manifest push --purge "$IMAGE:stable"

4. 验证发布结果​

4.1 检查远程平台列表​

发布完成后使用 Buildx 检查远程统一标签,应同时看到两个平台。即使没有用 Buildx 构建,imagetools inspect 仍是最清楚的验证工具:

# 读取远程统一标签,而不是本机缓存
docker buildx imagetools inspect "$IMAGE:$VERSION"

预期结果包含:

Manifests:
Platform: linux/amd64
Platform: linux/arm64

也可以检查 docker manifest 返回的 JSON。该命令主要用于诊断,日常发布日志优先保留上面的平台清单即可:

# 查看 manifest 的原始描述信息
docker manifest inspect "$IMAGE:$VERSION"

4.2 在真实或指定平台运行​

最可靠的验证是在各自的真实 CPU 架构上拉取并运行。以下命令适合检查当前主机自动选中的版本:

# Docker 会根据当前主机平台从统一标签中选择对应镜像
docker run --rm "$IMAGE:$VERSION" uname -m

需要在 Docker Desktop 或已配置 QEMU 的机器上做交叉功能验证时,可以显式指定平台:

# 在支持模拟的环境中验证 ARM64 变体;不用于性能基准
docker run --rm \
--platform linux/arm64 \
"$IMAGE:$VERSION" \
uname -m

输出常见为 x86_64(AMD64)或 aarch64(ARM64)。应用镜像的健康检查、启动参数和关键业务请求仍应在每种真实架构上通过 CI 验证。

5. CI 发布顺序​

将 manifest 发布任务设计为依赖两个架构构建任务的收敛步骤:

amd64 构建 + 测试 + 推送 api:<版本>-amd64 ─┐
├─ manifest 汇总 + 推送 api:<版本> ─> 远程验证
arm64 构建 + 测试 + 推送 api:<版本>-arm64 ──┘

发布任务应满足以下约束:

  1. 两个架构任务都成功后才创建统一标签;
  2. 两个源镜像使用同一份源码版本、同一依赖锁定文件和同一发布版本;
  3. 汇总前检查每个源标签的平台元数据,而不是只检查推送命令退出码;
  4. 将 manifest digest、源镜像 digest 和 CI 任务 ID 写入发布记录;
  5. stable 或 latest 等滚动标签只在不可变版本验证通过后更新。

下面的 Shell 片段可作为汇总任务的核心逻辑:

set -euo pipefail

# 先验证两个远程源镜像都存在且可解析
docker buildx imagetools inspect "$IMAGE:$VERSION-amd64" >/dev/null
docker buildx imagetools inspect "$IMAGE:$VERSION-arm64" >/dev/null

# 清理本地缓存后,创建、标注并发布统一标签
docker manifest rm "$IMAGE:$VERSION" 2>/dev/null || true
docker manifest create "$IMAGE:$VERSION" "$IMAGE:$VERSION-amd64" "$IMAGE:$VERSION-arm64"
docker manifest annotate "$IMAGE:$VERSION" "$IMAGE:$VERSION-amd64" --os linux --arch amd64
docker manifest annotate "$IMAGE:$VERSION" "$IMAGE:$VERSION-arm64" --os linux --arch arm64
docker manifest push --purge "$IMAGE:$VERSION"

# 发布完成后再次检查远程索引
docker buildx imagetools inspect "$IMAGE:$VERSION"
避免并发覆盖同一标签

如果多个流水线可能同时发布同一个 stable 或 latest 标签,需要在 CI 中为该环境标签设置互斥锁或并发组。不可变版本标签可以并行发布;同一个滚动标签不能由两个任务同时更新。

6. 常见问题​

no such manifest 或源标签找不到​

先确认镜像全名、仓库命名空间和架构后缀一致,再确认发布账号有读取源标签的权限:

# 这两条命令任何一条失败时,都不要创建统一 manifest
docker buildx imagetools inspect "$IMAGE:$VERSION-amd64"
docker buildx imagetools inspect "$IMAGE:$VERSION-arm64"

常见原因是 ARM64 Runner 推送到另一个项目路径,或 CI 变量展开后产生了不同版本标签。

拉取统一标签后提示 no matching manifest for linux/...​

统一标签中缺少当前平台,或 annotate 写错了 --os、--arch。重新使用 imagetools inspect 查看真实平台列表;不要只看仓库 UI 中是否存在同名标签。

denied: requested access to the resource is denied​

发布 manifest 既需要读取两个源镜像,也需要向统一标签所在仓库写入索引。检查机器人账号是否同时拥有读取和推送权限;跨项目汇总时,目标仓库可能还要求允许引用外部仓库的 manifest。

docker manifest 提示 experimental​

部分旧版 Docker CLI 仍将 docker manifest 标记为实验功能。优先升级 Docker CLI;无法升级时,使用 docker buildx imagetools create 也能完成镜像索引汇总,但团队应统一工具与发布脚本,避免两种流程同时维护。

源镜像被仓库清理策略删除​

多架构统一标签依赖每个架构源 manifest。若清理策略只保留统一标签却删除了 -amd64 或 -arm64 源标签,某些仓库可能仍保留底层 digest,也可能导致索引引用失效。为架构源标签设置与发布版本相同或更长的保留期,并定期执行拉取验证。

7. 发布检查清单​

  1. 每个目标架构都有不可变源标签,例如 1.2.0-amd64、1.2.0-arm64;
  2. 源标签都已推送到目标仓库,并通过 imagetools inspect 验证;
  3. 每个源镜像的操作系统和 CPU 架构元数据正确;
  4. 统一版本标签已通过 docker manifest create、annotate 和 push 发布;
  5. 远程统一标签同时列出 linux/amd64 与 linux/arm64;
  6. 至少在一种真实目标架构上完成运行验证;
  7. 发布记录保留版本、manifest digest、源镜像 digest 与 CI 任务信息。