机制 · 约 3 分钟
留下的层
镜像体积来自保留的文件层。基础镜像本身的文件也在其中,不只是你的代码。构建残留指运行时用不着的编译器、缓存、测试数据等。
层保存一次文件系统改动。新层能把旧文件隐藏,却不能改写已经保存的旧层。下面用 /tmp/cache 代表一份下载缓存;用方块表示文件是否存在,不表示大小。
- 左侧第一条 RUN 把程序和缓存一起保存为层。猜猜:下一条 RUN 删除缓存,能让已经保存的层缩小吗?
- 第二条 RUN 保存删除标记,不会回头修改第一层。
- 容器里看不到缓存了,镜像仍带着第一层的缓存数据。
- 右侧把下载、安装和清理放在同一条 RUN。此刻缓存只是构建中的临时文件。
- 在这条 RUN 结束前清理本次生成的缓存;尚未保存新增层。
- 最终只保存程序的文件改动;左侧的多余缓存依然留在旧层里。
先选满足运行需要的基础镜像,再排除多余文件:不要安装运行时用不着的包;用 .dockerignore 排除构建上下文里的无关文件;多阶段构建只把产物复制进最终阶段。参见 Docker 构建建议与多阶段构建。
深入一点:构建缓存和交付镜像是两个对象
多阶段构建的中间结果可以留在构建机缓存里,却不必进入最终镜像的层链。清理构建机缓存可能腾出本机空间,但不会重写你已经推送的镜像。要让交付镜像变小,应改变最终阶段保留的内容并重新构建。
如果最终阶段用 FROM build 继承整个构建阶段,构建工具也会被继承。图 1 假设最终阶段从单独的运行基础镜像开始。
动手 · 约 4 分钟
同口径比较
先固定平台和测量列,再谈谁更小。对比 debian:bookworm、debian:bookworm-slim 和 alpine:3.23,记录标签当时指向的摘要(digest,内容的哈希标识)。同一标签以后可能指向新内容。
编写环境没有 Docker CLI 和守护进程。以下是可复制实验及预期行为,没有实测大小和扫描结果。表格留待维护者补录,不能据空表计算排名或节省比例。
在宿主机的 Bash 中运行。需要 Docker Engine 29.x、已启动的 Linux 容器环境,以及拉取镜像的网络。下面选择 linux/amd64;ARM 机器请统一改为 linux/arm64,避免依赖跨架构模拟。
platform=linux/amd64
images=(debian:bookworm debian:bookworm-slim alpine:3.23)
date -u +%F
docker version
docker info --format '{{.Driver}} {{json .DriverStatus}}'
for image in "${images[@]}"; do
docker pull --platform "$platform" "$image"
docker image inspect --platform "$platform" \
--format '{{json .RepoDigests}} {{.Os}}/{{.Architecture}}' "$image"
docker image ls "$image"
docker run --rm --network none --platform "$platform" "$image" \
sh -c 'printf "runtime-ok\n"'
done
预期:每次 inspect 显示摘要和所选平台;每次 ls 有该镜像的一行。容器里的 printf 指定标准输出为 runtime-ok,共执行三次;这是预期输出,未实测。它只验证 shell 能运行,不证明你的应用兼容。
实验会下载镜像、占用本地磁盘并短暂创建容器。--rm 在命令退出后删除容器;--network none 让实验容器没有外部网络。镜像会保留,没有端口发布、宿主目录挂载或特权操作。
别把两列混起来
Docker 29 的人类可读列表在表头前有 WARNING: This output is designed for human readability... 提示(CLI 29.1.3 源码)。表头是 IMAGE ID DISK USAGE CONTENT SIZE EXTRA。这里只描述格式,不伪造一段实测终端。官方教程也展示了双列格式。
DISK USAGE:本地磁盘占用口径。CONTENT SIZE:镜像内容大小,通常对应压缩后的内容。它与解压后的文件大小不同。- 比较时始终用同一列,保留单位。已有共享层会影响实际新增下载量和独占磁盘占用,不能直接把各镜像的值相加。
若本地还保存了同一标签的其他平台,顶层行可能合计多个平台。用 docker image ls --tree "$image" 核对,读取目标平台子行,或在只保存目标平台的独立实验环境中比较;不要为本实验删除已有镜像。
| 镜像 | 比较目的 | DISK USAGE | CONTENT SIZE |
|---|---|---|---|
debian:bookworm | 完整变体,作为对照 | 未实测 | 未实测 |
debian:bookworm-slim | 同发行版的精简变体 | 未实测 | 未实测 |
alpine:3.23 | 另一发行版;需验证兼容性 | 未实测 | 未实测 |
三者都是操作系统基础镜像,不包含你的完整应用。选型还要补上运行库、证书和应用依赖,再测最终镜像。空白的扫描结果也不能当作“零漏洞”。
深入一点:为什么换机器可能测出不同值?
平台、标签指向、CLI 版本和存储后端都会影响记录。Docker Engine 29.0 起,全新安装默认用 containerd 镜像存储;从旧版升级可能仍用原来的后端。containerd 会保留压缩内容和解压后的快照,所以本地占用不能当作下载流量。参见存储说明。
若列表不符合本节表头,先记录实际客户端版本、存储后端和自定义格式;不要硬套本表。这里只测量,不要求修改 Docker 配置。
要定位哪条指令贡献体积,再读历史:
docker image history --platform "$platform" --no-trunc \
--format 'table {{.CreatedBy}}\t{{.Size}}' debian:bookworm-slim
预期是指令与大小的表格,最新历史在上方;这不是 ls 的双列格式。历史里的元数据指令可能不新增文件,不能只数行数判断体积。参见history 参考。
已经记录了相同平台和日期。要比较镜像内容大小,你应对三行都读取哪一列?
列名:
三行都读 CONTENT SIZE,并保留单位和摘要。DISK USAGE 也可作为另一组比较,但不能把一个镜像的 CONTENT SIZE 与另一个的 DISK USAGE 比。
你把“下载缓存”和“删除缓存”拆成两条 RUN,容器里已经找不到缓存。该怎样判断减重是否成功?
容器显示叠加后的文件视图。文件被隐藏,不表示旧层被改写。
后续层只能记录新的改动,不能把不可变的旧层改小。
需要让缓存在生成它的 RUN 结束前被删除,再重新构建。对照相同平台、同一大小列,才能判断新镜像是否减少了交付内容。
读报告 · 约 4 分钟
扫描读什么
扫描器从镜像里识别软件包和版本,再匹配已公开的漏洞公告。包清单叫软件物料清单(Software Bill of Materials,SBOM);漏洞编号常用公共漏洞与暴露(Common Vulnerabilities and Exposures,CVE)。
先保存全量,再筛重点
本页使用 Docker Scout CLI v1.29.0(截至 2026-11-03 的固定工具版本,未实测)。Docker Desktop 预装 Scout;单独的 Engine 需要按官方说明安装此版本。先运行版本命令核对;需要 Docker 账号登录和访问分析服务的网络。
docker scout version
docker login
scan_image=debian:bookworm-slim
docker image inspect --platform "$platform" \
--format '{{json .RepoDigests}}' "$scan_image"
docker scout cves --platform "$platform" "local://$scan_image" \
--output scout-full.txt
docker scout cves --platform "$platform" "local://$scan_image" \
--only-severity critical,high --only-fixed
接着上一节的 Bash 会话运行;local:// 强制使用本地镜像。预期第一遍写入全量报告 scout-full.txt,第二遍只显示“有修复版本的严重或高危”发现。本页未实测,不能预报条数。筛选后没有发现,可能只是其他等级或未修复的发现被隐藏了。参数见cves 参考。
复查时一并保存 UTC 日期、镜像摘要、平台、Scout 实际版本、命令及报告。固定工具版本不能固定服务端漏洞公告;同一摘要隔天复扫也可能不同。
从包追到修复版本
下面逐行摘自 Docker 官方教程的模拟终端,省略定位和其他漏洞。它用于读字段,非本页实测,也不表示这个包现在只有这一条漏洞。
- 包名与已装版本
pkg:npm/path-to-regexp@0.1.7标识 npm 生态里的这个包,版本为 0.1.7。npm 是 JavaScript 包管理生态;这里的包不是 Debian 的系统包。- 漏洞编号与等级
CVE-2024-52798指向具体公告。HIGH是高危;概览中 C / H / M / L 对应严重 / 高危 / 中危 / 低危,U 表示未指定。包数和漏洞数不相等,一个包可以命中多条公告。- 受影响范围与修复版本
Affected range是公告认定受影响的版本范围;Fixed version是对应修复版本。片段中 0.1.7 落在 <0.1.12 的范围内。升级越过这一条的修复边界,还要检查其余公告和应用兼容性。- 分数与实际风险
CVSS是通用漏洞评分系统(Common Vulnerability Scoring System),描述漏洞严重程度。它不证明你的运行路径一定可被利用。Scout 的等级与分数可能采用不同公告来源,应打开公告核实。参见等级来源说明。
某条 HIGH 发现的 Fixed version 显示 not fixed。加上 --only-fixed 后它不见了。你能据此宣布风险已消除吗?先写出理由。
不能。not fixed 表示当前公告没有列出修复版本,筛选器只是隐藏它。仍应保留全量报告,评估替换依赖、移除不需要的功能或限制暴露,并跟踪后续修复。字段含义见官方未修复示例。
决策 · 约 2 分钟
修复再验证
先找包从哪来,再改负责它的输入。基础镜像带来的系统包,要跟踪其维护者的更新;应用依赖则在项目依赖声明和锁定文件中更新。锁定文件记录实际解析出的依赖版本。
- 有修复版本:更新相应输入,重建并测试运行行为,再查这条发现是否消失。
- 没有修复版本:记录影响、临时措施与复查日期,持续跟进。不要把隐藏告警当成修复。
- 扫描为零:只说明当前工具在当前数据下未报出已知漏洞;不能证明没有未知漏洞、配置错误或应用逻辑缺陷。
深入一点:重建为什么还需要关注拉取与缓存?
在你自己的项目目录里,docker build --pull -t my-app:scan-check . 会检查 FROM 引用是否有更新;如果固定了摘要,需先按维护流程更新该摘要。--no-cache 会让构建步骤重新执行,但不会代替 --pull 刷新基础镜像。构建命令未实测,依赖你的项目 Dockerfile。
更新基础镜像也不自动修复你复制进去的应用包。反过来,更新应用包也不代替系统包更新。参见Docker 重建建议。
你把完整基础镜像换成 slim,体积下降,但同一应用包仍报 HIGH。用两句话解释原因,并给出下一步。
精简基础镜像减少了一部分系统文件,却未改变复制进去的应用依赖版本。先定位该包的公告与修复版本,更新项目依赖和锁定文件,再构建、运行测试并对新摘要复扫。
- 区分基础镜像文件和应用依赖。
- 指出体积减少不能直接证明漏洞修复。
- 包含重建、测试和对新镜像复扫。
回顾 · 约 2 分钟
要记住的
- 保留的基础层和新增文件决定体积;后层删除不改写旧层。
- 固定平台、日期和测量列,保存摘要;本页大小仍为未实测。
- 读包名与版本、CVE、等级、修复版本;筛选不能消除发现。
- 按来源更新,重建、运行测试、全量复扫;小镜像和零告警都不是安全保证。
精简后镜像更小。全量报告里仍有一条“有修复版本的 HIGH”。哪项最能支持交付决定?
镜像可能少了无关文件,但受影响的包和版本仍然存在。
这条发现有修复版本,会被 --only-fixed 保留。筛选本身不会改变包版本。
应把报告转成可核验的改动。更新正确来源,测试新镜像的行为,并保存新摘要、工具版本、日期和全量报告,才能判断这次修复的效果。
来源
以下来源于 2026-11-03 核对。命令按 Docker Engine 29.x;Scout 选择固定版本 v1.29.0。官方教程中的模拟输出只用于字段教学。
- Docker Docs:Building best practices;Multi-stage builds。基础镜像、同层清理、重建与阶段边界。
- Docker Docs:Storage drivers;containerd image store。不可变层、共享数据、压缩内容与本地存储。
- Docker Learn:Shrink your image,Step 5。双体积列;image ls、inspect、history 命令参考。
- Docker Official Images 标签登记:Debian、Alpine。仅据此选择实验标签,没有引用或推算大小。
- Docker:Scout CLI v1.29.0 发布记录;安装说明。
- Docker Docs:Scout image analysis、scout cves。包匹配、等级来源、筛选与报告保存。
- Docker Learn:Find and fix vulnerabilities before you ship。本页扫描片段的原文出处,以及扫描边界。