
前言
很多人第一次看到 Docker 镜像大小时,都会有同样的困惑:
- 为什么 CONTENT SIZE 只有 3GB,DISK USAGE 却有 10GB?
docker pull到底要下载多少流量?- 改了一个 5GB 的大文件只改了 1KB,为什么镜像像"翻倍"了一样?
docker image prune、docker builder prune、docker system prune到底有什么区别?.dockerignore能解决什么问题?
如果你也有这些疑问,这篇文章会把 Docker 镜像体积相关的核心概念一次讲透。
一、先记住一个总原则
Docker 的体积问题,核心不是"文件本身大不大",而是下面这三件事:
- 镜像是否被分层存储
- 这些层是否能复用
- 构建上下文是否被正确过滤
Docker 最重要的设计目标不是"极致压缩",而是:
- 可复现
- 可缓存
- 可复用
- 可增量分发
也就是说,Docker 更像一个"分层快照系统",而不是一个"智能差分压缩系统"。
二、CONTENT SIZE 和 DISK USAGE 到底是什么
1. CONTENT SIZE
CONTENT SIZE 可以理解为:
这个镜像自身"逻辑上"包含的数据量。
它更接近"镜像本体"的大小,偏向于 远程仓库分发视角 或者 镜像内容总量视角。
你可以把它理解成:
- 这个镜像自己需要哪些 layer
- 这些 layer 独立看有多大
- 不考虑本机已经有没有这些 layer
2. DISK USAGE
DISK USAGE 指的是:
Docker 在你本地磁盘上实际占用了多少空间。
它往往比 CONTENT SIZE 更大,因为它可能包含:
- 多个镜像共享但重复保留的层
- 旧版本镜像
- stopped container 的可写层
- build cache
- dangling image
- volume 数据
- 历史残留
所以你会看到:
- CONTENT SIZE 只有 3GB
- DISK USAGE 却达到 10GB
这并不奇怪,说明本地 Docker 环境已经"积累了历史包袱"。
三、Docker 镜像为什么是"分层"的
Docker 镜像不是一个完整的大文件,而是由多个 layer 叠加组成的。
每一层都记录了相对于上一层的变化。
例如:
- 基础系统层:Ubuntu
- 语言运行时层:Python / Node.js
- 依赖层:pip install / npm install
- 业务代码层:COPY . .
这些层会被组合起来,形成最终可运行的镜像。
镜像分层结构示意
四、什么是 shared layer,什么是 unique layer
1. Shared layer
Shared layer 是多个镜像都能复用的层。
例如:
ubuntu:22.04python:3.12- 常见系统库
如果多个镜像都用到了同一层,Docker 只需要保存一份。
2. Unique layer
Unique layer 是某个镜像独有的层。
比如:
- 你的业务代码
- 特定版本的依赖
- 某次构建中新产生的内容
- 修改后的大文件
Unique layer 不是"只有一层",而是:
这个镜像独有的那些 layer 的集合。
所以它可能是 1 层,也可能是 3 层、5 层甚至更多。
五、一个最容易误解的问题:pull 到底下载多少流量
结论先说
docker pull 下载的不是你看到的 DISK USAGE 那么多,而是:
本地没有的 layer。
也就是说,下载量大致等于:
- 这个镜像需要的 layer 中
- 你本机当前还没有的那些 layer
- 再加上这些 layer 的压缩传输体积
重要理解
Docker pull 不是"下载一个大包",而是"按 layer 下载"。
如果你本地已经有某些 layer,那么 pull 时会跳过它们,只下载缺失的部分。
pull 流程示意
六、为什么 CONTENT SIZE 是 3GB,DISK USAGE 却可能是 10GB
常见原因有这些:
1. 镜像之间共享层不止一份
你可能有多个镜像版本:
app:latestapp:v1app:v2
它们共享基础层,但各自又有不同的业务层。
共享层只存一份,但如果你在本机积累了很多版本,总占用会迅速上升。
2. build cache 很大
构建过程中,Docker 会保存中间产物和缓存层。
这些缓存层:
- 不是最终镜像内容
- 但会占用磁盘
- 常常是"DISK USAGE 大户"
尤其在频繁 build 的项目里,build cache 很容易堆积到好几 GB。
3. stopped containers 也占空间
容器运行时有可写层,如果容器停止后没有清理,它也会保留数据。
里面可能包括:
- 日志
- 临时文件
- 运行时生成的数据
4. volume 数据
如果你把数据库、缓存、附件挂到了 volume,上面的数据不会算在某个镜像里,但会占 Docker 的磁盘空间。
5. dangling image / 历史残留
一些中间镜像没有标签,但还在本地保留着。
它们往往看不见、摸不着,但就是在占空间。
七、改了一个大文件一小部分,为什么镜像会"翻倍"
这是很多人最困惑的一点。
关键答案
Docker 的 layer 不是"块级差分",更接近"文件级快照"。
也就是说:
- 你改了一个大文件的一小部分
- Docker 往往会把这个文件重新写入新 layer
- 新 layer 里会出现该文件的新版本
如果这个文件本身很大,比如 5GB,那么即使你只改了 1KB,新的 layer 也可能接近 5GB。
结果
- 旧 layer:5GB
- 新 layer:5GB
- 总计:接近 10GB
这就是"镜像翻倍感"的来源。
这和 git 一样吗?
不一样。
git 更偏向"内容差异和历史版本管理",而 Docker layer 更偏向"文件系统快照和分层复用"。
Docker 的目标不是做最强增量压缩,而是保证:
- 构建简单
- 缓存可复用
- 镜像可重现
- 分发可校验
八、Docker 有没有压缩机制
有,但要分清楚"压缩发生在哪"。
1. 传输时有压缩
在 docker pull / docker push 时,layer 通常会以压缩格式传输。
这意味着网络流量一般会比"原始内容大小"更小。
2. 存储层不是"把两个 layer 合起来智能压缩"
Docker 不会为了更高压缩率,把多个 layer 合成一个超大压缩包再分发。
因为这样会失去:
- 层复用
- 增量下载
- 缓存命中
- 内容寻址
所以 Docker 选择的是"分层复用",而不是"极致整体压缩"。
九、为什么不能把两个 layer 一起压缩
从压缩算法角度看,两个 layer 一起压缩,也许能压得更小一点。
但是 Docker 的核心价值不在这里,而在于:
- 同一层能被多个镜像共享
- 本地已有的 layer 可以跳过下载
- 某层没有变,就不必重新传输
如果把多个 layer 合并压缩:
- 共享性会下降
- cache 命中率会变差
- 拉取时常常要重新下载整个大包
所以 Docker 不这么设计。
十、prune 系列命令到底有什么区别
Docker 清理命令很多,但其实可以分成几类。
1. docker image prune
清理 dangling images,也就是没有标签、没有引用的镜像。
适合:
- 清理无名中间镜像
- 回收少量空间
2. docker image prune -a
清理所有未被容器使用的镜像。
比 docker image prune 激进得多。
适合:
- 你明确不需要旧镜像
- 想快速释放空间
3. docker container prune
清理停止状态的容器。
不会删正在运行的容器。
4. docker volume prune
清理未被使用的数据卷。
这个最危险,因为 volume 里经常是:
- 数据库数据
- 附件
- 缓存
- 用户持久化内容
执行前要非常小心。
5. docker builder prune
清理 build cache。
这通常是最值得先清理的对象,因为它往往占空间很大,而且相对安全。
6. docker system prune
综合清理:
- 停止容器
- dangling images
- 未使用网络
- 部分缓存
这是"常用的一键清理"。
7. docker system prune -a
最激进的清理方式。
会删除大量未使用对象,适合:
- 环境已经很乱
- 你准备大清场
但使用时要先确认是否会影响当前开发。
prune 关系图
十一、.dockerignore 的作用是什么
.dockerignore 的作用非常关键:
控制哪些文件不会被发送到 Docker build 上下文里。
这是很多人减小镜像体积和加快 build 的第一步。
你可以理解为
docker build . 的时候,当前目录会作为构建上下文发送给 Docker。
如果不写 .dockerignore,很可能:
- 把
.git传进去 - 把
node_modules传进去 - 把
.env传进去 - 把模型文件传进去
- 把临时文件传进去
这会导致:
- build 慢
- 上下文大
- 镜像容易膨胀
- 敏感信息可能被带进构建流程
常见 .dockerignore 示例
Node / 前端项目
dockerignore
node_modules
dist
build
.git
.env
*.log
Python / AI 项目
dockerignore
__pycache__/
*.pyc
.venv
venv/
.git
.env
logs/
tmp/
models/
*.pt
*.bin
*.onnx
.dockerignore 生效位置
十二、为什么 .dockerignore 很适合 AI 项目
如果你做的是 AI 项目,特别容易遇到大体积文件:
- embedding 模型
- reranker 模型
- ONNX 文件
- tokenizer 缓存
- 实验产物
- 日志
这些东西如果被错误地放进镜像里,镜像会非常大。
很多 AI 项目镜像体积膨胀,不是因为代码多,而是因为:
大模型文件被一并打包进了 build context 或镜像层。
所以 AI 项目一定要认真写 .dockerignore。
十三、一个推荐的镜像优化思路
如果你希望镜像更小,可以按这个思路来:
1. 基础层尽量小
- 选轻量镜像
- 避免不必要的系统包
2. 依赖安装分层优化
先复制依赖清单,再安装依赖,最后复制代码。
这样能最大化缓存复用。
3. 大文件不要直接打入镜像
- 模型文件用挂载
- 数据用 volume
- 外部下载
4. 配好 .dockerignore
把不需要的内容挡在 build 之外。
5. 定期 prune
避免 build cache 堆积。
十四、实战建议:如何判断你的 Docker "脏不脏"
你可以先执行:
bash
docker system df -v
重点看这些项:
- Images
- Containers
- Local Volumes
- Build Cache
如果 Build Cache 很大,通常先清它。
如果 Volume 很大,先确认里面有没有业务数据再动。
如果 Images 很大,看看是不是有多个旧版本镜像堆积。
十五、推荐的清理顺序
一个比较稳妥的顺序是:
docker builder prunedocker container prunedocker image prune- 视情况使用
docker image prune -a - 最后再考虑
docker volume prune
这套思路通常能先回收最多空间,同时降低误删风险。
十六、总结:把这几个概念记住就够了
1. CONTENT SIZE
镜像自身逻辑大小,更接近"镜像内容总量"。
2. DISK USAGE
本地 Docker 实际占用空间,包含镜像、缓存、容器、卷、历史残留等。
3. UNIQUE layer
该镜像独有的层,不一定只有一层,而是一组独有层。
4. docker pull
只下载你本地没有的 layer,不是盲目下载全部。
5. 修改大文件会膨胀
Docker 更接近文件级快照,改一个大文件的一小部分,可能导致一个新大层。
6. .dockerignore
控制构建上下文,防止无关文件和大文件进入镜像。
7. prune
清理不同类型的垃圾:镜像、容器、卷、缓存,各有各的风险。
十七、可以直接拿走的结论
Docker 镜像体积不是单纯看一个数字,而是看层复用、缓存、构建上下文和本地残留。
pull 的流量通常接近"缺失 layer 的压缩后体积",而不是本机 DISK USAGE。
AI 项目要特别注意大模型文件、缓存和.dockerignore,否则体积会非常夸张。