bigywater.tech
Docker · 18

镜像摘要与标签:同一个 tag 为什么可能指向不同内容

同一个 tag 为什么会指向不同的内容?这篇用本机构建的镜像实际看:镜像 ID 和 digest 是什么关系,docker tag 和重新 docker build 对旧镜像的处理有什么不同,name@digest 在本机什么时候找得到,以及“同一个 Dockerfile 是不是同一个镜像”。

f5b5… vs 1d96…
v1 和 v2 的镜像 ID(也是顶层 manifest 摘要),tag 挪走之后 v1 还能按 ID 运行
3 个 vs 1 个 ID
全缓存重复构建三次:默认得到 3 个不同的 ID,关掉 provenance 和 sbom 后只有 1 个
退出码 125
name@digest 引用一个已经没有这个名字的镜像:No such image

ID 与 digest

实测 · 本机执行输出
  构建 v1 并命名 bwlab-c13:a:ID = f5b5f81ecbb1…(完整形式 sha256: 加 64 位十六进制,共 71 个字符)
  .Id 和 .Descriptor.digest 相同吗?True(这个镜像存储里镜像 ID 就是顶层 manifest 的摘要)
  RepoDigests = ["bwlab-c13@sha256:f5b5f81ecbb1c4b918634afedde042d92d3a5c90e3dee4cccf1bb082bc27f516"](没有推送过)
  再构建 v2 命名为 bwlab-c13:b:ID = 1d96e82a084a…

构建 v1 命名为 bwlab-c13:a,.Id 与 .Descriptor.digest 是同一个值;RepoDigests 里即使没推送过也有一项 bwlab-c13@sha256:…。这是 containerd 镜像存储下的表现;换成经典的存储驱动,ID 的含义是否一样,本文没有测。

docker tag 挪名字

实测 · 本机执行输出
  docker tag bwlab-c13:b bwlab-c13:a → 退出码 0
  现在 bwlab-c13:a 的 ID = 1d96e82a084a…,和 bwlab-c13:b 相同吗?True
  docker image ls -a 里出现的 <untagged> 镜像:['f5b5f81ecbb1'](旧 v1 的 ID 是 f5b5f81ecbb1)
  按旧 ID 运行 docker run --pull=never f5b5f81ecbb1 → 退出码 0;输出:v1

docker tag bwlab-c13:b bwlab-c13:a 之后,两个名字指向同一个 ID;原来 a 指向的 v1 变成 <untagged>,但镜像还在,docker run --pull=never 加上旧 ID 能跑出 v1。同一个 tag 在不同时间指向不同内容,就是这么来的。

name@digest 在本机

实测 · 本机执行输出
  docker run --pull=never bwlab-c13@sha256:f5b5f81ecbb1… → 退出码 125;docker: Error response from daemon: No such image: bwlab-c13@sha256:f5…
  docker run --pull=never bwlab-c13@sha256:1d96e82a084a… → 退出码 0;输出:v2
  引用一个不存在的摘要 → 退出码 125;docker: Error response from daemon: No such image: bwlab-c13@sha256:000000000000000000000000000000000000000000

被挪走名字的 v1,用 bwlab-c13@sha256:f5b5… 引用,--pull=never 下报 No such image(退出码 125);当前叫 bwlab-c13 的 v2 用它自己的摘要引用就能运行。本机的 name@digest 解析要求这个镜像此刻还带着这个名字。摘要在仓库里“钉住”内容的作用,本文没有实验(需要 registry)。

docker build 同名

实测 · 本机执行输出
  同一个 tag bwlab-c13:c 先后构建两次:ID 8656f6b6bd5f → 1c31d8e2e739
  按第一次的 ID 运行 → 退出码 125;docker: Error response from daemon: No such image: 8656f6b6bd5f:latest
  docker image ls -a 里的 <untagged>:['f5b5f81ecbb1']

重新构建同一个 tag 和 docker tag 挪名字不一样:这个环境里旧镜像没有留下 <untagged>,按旧 ID 运行也找不到。我没有查是哪一层把它删掉的,只记录观察到的结果。

同一个 Dockerfile

实测 · 本机执行输出
  两次构建的镜像 ID:886d739dd530 和 cfd9225cfd6e;相同吗?False
  RootFS.Layers 共 2 层;alpine 基础层相同吗?True;RUN 产生的那层相同吗?False

两次 --no-cache 构建同样的 Dockerfile,镜像 ID 不同,alpine 基础层相同,RUN 产生的层不同。要得到可复现的构建,需要额外控制时间戳之类的输入(本文没有测怎么做)。

命中缓存也会变

实测 · 本机执行输出
  默认(带 provenance 证明):三次构建的 ID = ['f36ba2a729ba', '2ef9bec6124a', '4ddb08e30de0'];ID 互不相同的有 3 个;层(RootFS.Layers)都相同吗?True
  --provenance=false --sbom=false:三次构建的 ID = ['af13472dcb8a', 'af13472dcb8a', 'af13472dcb8a'];ID 互不相同的有 1 个;层(RootFS.Layers)都相同吗?True

三次全缓存的构建,默认得到 3 个不同的镜像 ID,但层完全一致;加上 --provenance=false --sbom=false 后三次 ID 相同。结合之前 c11 看到的 attestation manifest,我推测是附带的构建证明让顶层摘要变了,没有单独验证是 provenance 还是 sbom 起作用。想用摘要比较两个镜像“内容是否相同”时,这点要小心。

分步演示

镜像摘要与标签
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。基础镜像 alpine:3.20 用 dpull.py 下载后 docker load;所有镜像在本机构建,没有推送;ID 取 docker image inspect 的 .Id,层取 .RootFS.Layers;docker run 用 --pull=never 避免去访问 registry;实验后已删除所有镜像
    • 没有验证:推送到仓库后的摘要与 RepoDigests、经典存储驱动下的 ID 含义、docker pull 时摘要如何校验、缓存命中的构建 ID 变化到底来自 provenance 还是 sbom、用 SOURCE_DATE_EPOCH 等方式得到可复现构建
    京ICP备2026067502号-1京公网安备11010502063487号