← 全部笔记

Docker · 8 安全与生产实践

标签会移动,
digest 锁定内容

为什么别用 latest?生产部署需要记录“实际用了哪份镜像”,仅有标签还不够。

约 15 分钟 · 截至 2026-11-03,Docker Engine 29.x · 实验未实测

标签移动,digest 引用保留原内容 在同一个镜像集合中,发布前 latest 指向 A,发布后指向 B;固定 A 的 digest 的引用始终选择 A。A 和 B 是内容的示意代号。 同一镜像集合 · 指针示意 :latest 发布前 内容 A :latest 发布后 内容 B @digest A 仍选 A
图 1 标签能改指向;同一个 digest 始终标识同一内容。A、B 是示意代号。

先拆开两个误解

latest 只是名字

标签(Tag)是可移动的指针。发布者可以把它重新指向另一份镜像。latest 没有“找出版本号最大者”的特殊能力:它指向什么,由发布者决定。[1][2]

省略标签时,docker pull alpine 使用 alpine:latest。如果发布者只推送了一个版本标签,Docker 不会因此自动更新 latest;如果根本没有这个标签,拉取会失败。

发布者推送了新版本标签,却没有移动 latest。你执行 docker pull image:latest,会自动找到那个新版本吗?写下你的理由。

不会。你请求的是名为 latest 的指针,Docker 不会把其他标签按版本排序再替你选择。要知道它代表哪个版本,先看发布者的标签说明,再核对实际 digest。

远端指针与本地缓存是两回事

docker run 默认使用 --pull=missing:本地已有对应镜像时直接使用。本地没有时才拉取。即使远端标签移动了,本地也可能仍是旧内容。[3]

docker run --pull=always … 会先拉取,再创建容器。但它只能取得该标签当前所指的内容,不能让标签变成固定版本。拉取或移动标签也不会替换已经创建的容器;更新容器需要重新创建。

需要回顾对象关系时,读镜像、容器与仓库;拉取与创建的顺序见第一次 docker run。

从名字到内容身份

digest 固定内容

摘要(Digest)是根据内容计算的标识。常见形式是 sha256: 后接 64 个十六进制字符。内容改变,对应的摘要也会改变。用 image@sha256:… 拉取时,你指定的是内容,而非可移动的标签。[4]

下面用 D_A、D_B 代替两份内容的完整摘要。它们只用于图示,不能复制到命令里。

标签移动后,按旧 digest 仍选 A 六步演示发布 A、记录摘要、按标签拉取、固定摘要、标签移动到 B,以及按旧摘要创建 A 的新容器。图中始终在同一平台上比较。 远端镜像集合 · 同一平台示意 :latest 内容 A 指向 D_A 此前按标签取 A 本地缓存 A 部署记录:image@D_A 内容 BD_B 发布后改指向 B 按 D_A 选 A 新容器用 A 按 D_A 获取,避开标签变化
  1. 发布者先让 latest 指向内容 A。预测:给 A 另加一个标签,会改变 A 的内容吗?
  2. 不会。内容 A 的摘要仍是 D_A;标签只是额外的名字。
  3. 按标签拉取时,Docker 此时得到 A,并保存到本地。
  4. 把 image@D_A 写进部署记录。预测:远端 latest 改指向 B 后,这个记录会选谁?
  5. 发布者移动 latest 到 B。改变的是标签指针,A 的摘要没有变。
  6. 按 D_A 创建新容器,仍选择 A。若本地缺失且仓库也不再提供 A,则拉取失败;不会悄悄换成 B。
图 2 只有固定内容摘要,才能跨过标签变化选择原来的内容;图中 D_A、D_B 为占位代号。

同理,写得很具体的版本标签也只是标签。即使应用版本号不变,发布者仍可能重建基础系统并重新推送。image:tag@sha256:… 可以保留可读标签,实际内容由摘要确定;只写 image@sha256:… 也可以。[5]

深入:digest、IMAGE ID 与多平台镜像

清单(Manifest)描述镜像配置与各层;镜像索引(Image Index,也称清单列表)把多个平台的清单放在一起。仓库 digest 可以标识清单或索引,不能把本地 IMAGE ID 直接当成仓库引用使用。

固定索引 digest,固定的是整组平台内容。Docker 仍会按宿主机平台选其中一份,例如 linux/amd64 或 linux/arm64。需要严格复现时,连同平台一起记录;不要期待不同架构下载完全相同的文件。[6]

不可变不等于永久可下载:仓库可能删除内容,或你可能失去访问权限。要能回退,还需保留旧镜像。固定摘要也不保证程序无漏洞或来源可信。

同一平台上,昨天记录了 image@D_A,今天 latest 指向 B。你按记录拉取,可能发生什么?

请求里没有 latest。Docker 按指定摘要取内容,不追踪别名。

摘要保证内容身份,不保证远端保留期限、权限或网络可用性。

固定摘要排除了指针变化。内容若可取回,仍是 A;取不回时只能报错。内容身份与可用性是两件事。

本机实验 · 约 4 分钟

取摘要再运行

在有 Docker 的 Linux 或 macOS 终端中使用 Bash。Docker Desktop 需处于 Linux 容器模式。alpine:3.21 来自官方文档的示例系列,不是最新版本推荐。[5]

未实测

本页编写环境没有 Docker。以下是可复制的实验命令与预期检查项,未提供实测终端输出。请由维护者补实测;实际摘要与下载日志以你的机器为准。

前提是 Docker 服务可用,且你能访问 Docker Hub。实验会下载镜像并占用本地缓存;不发布端口、不挂载宿主机目录。运行时用 --network none,不接入默认网桥;下载镜像仍需要宿主机联网。

① 拉取,并查看仓库引用

docker pull alpine:3.21
docker image inspect alpine:3.21 --format '{{range .RepoDigests}}{{println .}}{{end}}'

预期检查项(未实测):拉取成功后,inspect 应列出包含 @sha256: 的仓库引用。RepoDigests 是本地记录的仓库摘要引用列表;这里读取它,而不依赖 docker image ls 的人类可读列布局。[7]

② 保存完整引用,再按摘要拉取

以下代码在同一个 Bash 终端继续执行;只在前一步成功后继续。image_ref 是你本次实验的变量,不是预先写死的摘要。

image_ref=$(docker image inspect alpine:3.21 --format '{{index .RepoDigests 0}}')
printf '%s\n' "$image_ref"
# 确认打印的是 alpine 对应的完整仓库引用,含 @sha256:。
# 若为空或 inspect 报错,先停止并检查前一步拉取。
docker pull "$image_ref"

预期检查项(未实测):记录变量里的完整引用。第二次拉取应按这个摘要获取原内容,即使之后 3.21 标签移动也不会改选。不要从网页图中复制 D_A,也不要手写一个省略了中间字符的摘要。

③ 从固定引用启动临时容器

docker run --rm --network none "$image_ref" sh -c 'printf "digest-pinned\n"'

预期检查项(未实测):容器里的 printf 应打印命令指定的 digest-pinned 并换行,然后退出。--rm 删除退出的容器,已下载的镜像仍留在本地。运行成功只是确认该引用可用;固定内容的依据是完整摘要,而非这行文字。

可选:证明两个标签能指向同一份本地内容
docker tag "$image_ref" local/tag-lab:sample
docker tag "$image_ref" local/tag-lab:latest
docker image inspect local/tag-lab:sample --format '{{.Id}}'
docker image inspect local/tag-lab:latest --format '{{.Id}}'
# 可选清理:只移除这两个实验标签,不使用强制删除。
docker image rm local/tag-lab:sample local/tag-lab:latest

预期检查项(未实测):两个 .Id 相同。docker tag 只创建本地引用,不复制镜像内容,也不自动推送远端。[1] 这里比较本地 ID 来验证别名关系;部署时仍使用前面取得的仓库摘要引用。

不用重看命令:镜像名和内容摘要之间,用什么符号连接?

image sha256:完整摘要

: 接标签,@ 接摘要。这里填写的是语法符号,图中省略的摘要不能直接运行;实际命令要用实验取得的完整引用。

为名字约定含义

标签表达意图

语义化版本(Semantic Versioning,SemVer)用 主版本.次版本.修订号 表达兼容性:主版本表示不兼容变更;次版本表示兼容的新功能;修订号表示兼容的问题修复。[8]

按这个约定,文档示例版本 3.21.4 可拆成主版本 3、次版本 21、修订号 4。但 Docker 不会执行 SemVer 规则,也不会自动为标签创建版本范围。

一种发布约定示意:假设你的应用采用这些标签;不是 Alpine 实际标签清单。
引用发布者约定使用前要知道
app:latest默认发布通道不承诺版本大小,也不承诺兼容性。
app:3跟随主版本 3 的更新可能跨次版本,需测试功能变化。
app:3.21跟随这一系列的修复适合发现更新,仍可能因重建而改变。
app:3.21.4标记一次应用发布更易读;仓库仍可能允许覆盖它。
app@sha256:…固定一份内容适合记录部署;更新要显式更换摘要。

选择第三方镜像时,先读它自己的标签说明。stable、alpine 等名字也是发布者的约定;后缀可能区分基础系统。不能只凭数字判断系统变体与兼容性。

按引用本身提供的保证分类,不额外假设仓库禁止覆盖标签。

  • app:latest
  • app:3.21
  • app:3.21.4
  • app@sha256:完整摘要(语法示意)
可变名字
固定内容

标签描述你想跟随的版本或通道;digest 标识实际选中的内容。版本标签能帮助人读懂变更,但不能单独保证字节不变。

让更新成为一次明确的变更

固定后主动更新

生产部署记录标签含义、完整 digest 和平台。把这份记录放进版本控制;每次发布只部署经过验证的内容。固定摘要后,安全补丁不会自动进入现有引用。[5]

  1. 发现 →关注发布说明与漏洞修复,取得候选摘要。
  2. 验证 →核对来源、扫描并测试候选内容。
  3. 批准 →提交摘要变更,记录标签、平台与测试结果。
  4. 部署 / 回退按新摘要重建容器;保留旧摘要与镜像。
图 3 更新是把已验证的新摘要写入部署记录,再创建新容器。

对自己发布的镜像,可以规定发布标签不覆盖,另设可移动的版本系列标签。消费方仍记录 digest。回退时选择上一份已验证摘要;若涉及数据库结构变化,还需单独验证数据兼容性,换回镜像不等于还原数据。

同事说“已经固定 digest,以后不用管更新了”。你会怎样解释,并提出一个能执行的更新流程?

固定摘要让你控制变更时机,但也锁住了旧依赖。要定期发现修复、验证新镜像,再通过一次可审查的摘要变更发布。

  • 说明旧摘要不会自动包含新补丁。
  • 提出发现更新、测试、批准、更换摘要的步骤。
  • 保留旧镜像和记录,并检查数据能否回退。

合上页面前

要记住的

测试环境验证了某份镜像,生产下周发布且必须使用同一份内容。哪种做法最可靠?

always 保证先拉取;它会跟随下周的标签指向,无法保证还是已测内容。

具体版本有助于理解,但版本标签仍可能被覆盖或重建。

把已验证的内容身份传到生产,才能避免两个环境在不同时间解析标签。主动验证新摘要可以同时保留升级控制和安全更新;也要确保目标仓库保留了需要的内容。

来源

核查日期:2026-11-03。命令与默认行为以 Docker 官方文档为准;实验未实测,示意图不代表某镜像当前的标签状态。

  1. Docker Docs · docker image tag:引用结构、默认 latest 与本地别名。
  2. Docker · Using tags and labels to manage image sprawl:latest 不保证最新版本。
  3. Docker Docs · docker run 的拉取策略:missing、always、never 与容器创建。
  4. Docker Docs · 按 digest 拉取:固定内容与更新边界。
  5. Docker Docs · Pin base image versions:标签可变、3.21 系列的例子、tag@digest 和更新审查。
  6. Docker Docs · 多平台镜像结构:清单列表及平台选择。
  7. Docker Docs · docker image inspect、Engine API 的 RepoDigests 字段 与 镜像引用:读取镜像信息与摘要引用。
  8. Semantic Versioning 2.0.0:主版本、次版本、修订号的约定。