Docker 镜像 SIZE、DISK USAGE、Layer、Prune、.dockerignore 全面解析:pull 到底下载多少?

4 阅读10分钟

Gemini_Generated_Image_2vkkh82vkkh82vkk.png

前言

很多人第一次看到 Docker 镜像大小时,都会有同样的困惑:

  • 为什么 CONTENT SIZE 只有 3GB,DISK USAGE 却有 10GB?
  • docker pull 到底要下载多少流量?
  • 改了一个 5GB 的大文件只改了 1KB,为什么镜像像“翻倍”了一样?
  • docker image prunedocker builder prunedocker system prune 到底有什么区别?
  • .dockerignore 能解决什么问题?

如果你也有这些疑问,这篇文章会把 Docker 镜像体积相关的核心概念一次讲透。


一、先记住一个总原则

Docker 的体积问题,核心不是“文件本身大不大”,而是下面这三件事:

  1. 镜像是否被分层存储
  2. 这些层是否能复用
  3. 构建上下文是否被正确过滤

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 . .

这些层会被组合起来,形成最终可运行的镜像。

镜像分层结构示意

flowchart TB
    A[Base Image\nUbuntu] --> B[Runtime Layer\nPython / Node]
    B --> C[Dependencies Layer\npip install / npm install]
    C --> D[App Code Layer\nCOPY source code]
    D --> E[Final Image]

四、什么是 shared layer,什么是 unique layer

1. Shared layer

Shared layer 是多个镜像都能复用的层。

例如:

  • ubuntu:22.04
  • python: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 流程示意

flowchart TD
    A[开始 docker pull] --> B{本地是否已有该 layer?}
    B -- 有 --> C[跳过下载]
    B -- 没有 --> D[下载该 layer]
    C --> E[继续检查下一层]
    D --> E
    E --> F[合并所有需要的 layer]
    F --> G[得到镜像]

六、为什么 CONTENT SIZE 是 3GB,DISK USAGE 却可能是 10GB

常见原因有这些:

1. 镜像之间共享层不止一份

你可能有多个镜像版本:

  • app:latest
  • app:v1
  • app: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 关系图

flowchart TB
    A[Docker 清理命令] --> B[image prune]
    A --> C[image prune -a]
    A --> D[container prune]
    A --> E[volume prune]
    A --> F[builder prune]
    A --> G[system prune]
    A --> H[system prune -a]

    B --> B1[dangling images]
    C --> C1[未被容器使用的镜像]
    D --> D1[stopped containers]
    E --> E1[未使用 volumes]
    F --> F1[build cache]
    G --> G1[综合清理]
    H --> H1[最激进清理]

十一、.dockerignore 的作用是什么

.dockerignore 的作用非常关键:

控制哪些文件不会被发送到 Docker build 上下文里。

这是很多人减小镜像体积和加快 build 的第一步。

你可以理解为

docker build . 的时候,当前目录会作为构建上下文发送给 Docker。

如果不写 .dockerignore,很可能:

  • .git 传进去
  • node_modules 传进去
  • .env 传进去
  • 把模型文件传进去
  • 把临时文件传进去

这会导致:

  • build 慢
  • 上下文大
  • 镜像容易膨胀
  • 敏感信息可能被带进构建流程

常见 .dockerignore 示例

Node / 前端项目

node_modules
dist
build
.git
.env
*.log

Python / AI 项目

__pycache__/
*.pyc
.venv
venv/
.git
.env
logs/
tmp/
models/
*.pt
*.bin
*.onnx

.dockerignore 生效位置

flowchart LR
    A[本地项目目录] --> B[docker build 上下文打包]
    B --> C{.dockerignore 过滤}
    C -->|保留| D[发送给 Docker]
    C -->|忽略| E[不发送]
    D --> F[COPY 到镜像]

十二、为什么 .dockerignore 很适合 AI 项目

如果你做的是 AI 项目,特别容易遇到大体积文件:

  • embedding 模型
  • reranker 模型
  • ONNX 文件
  • tokenizer 缓存
  • 实验产物
  • 日志

这些东西如果被错误地放进镜像里,镜像会非常大。

很多 AI 项目镜像体积膨胀,不是因为代码多,而是因为:

大模型文件被一并打包进了 build context 或镜像层。

所以 AI 项目一定要认真写 .dockerignore


十三、一个推荐的镜像优化思路

如果你希望镜像更小,可以按这个思路来:

1. 基础层尽量小

  • 选轻量镜像
  • 避免不必要的系统包

2. 依赖安装分层优化

先复制依赖清单,再安装依赖,最后复制代码。

这样能最大化缓存复用。

3. 大文件不要直接打入镜像

  • 模型文件用挂载
  • 数据用 volume
  • 外部下载

4. 配好 .dockerignore

把不需要的内容挡在 build 之外。

5. 定期 prune

避免 build cache 堆积。


十四、实战建议:如何判断你的 Docker “脏不脏”

你可以先执行:

docker system df -v

重点看这些项:

  • Images
  • Containers
  • Local Volumes
  • Build Cache

如果 Build Cache 很大,通常先清它。

如果 Volume 很大,先确认里面有没有业务数据再动。

如果 Images 很大,看看是不是有多个旧版本镜像堆积。


十五、推荐的清理顺序

一个比较稳妥的顺序是:

  1. docker builder prune
  2. docker container prune
  3. docker image prune
  4. 视情况使用 docker image prune -a
  5. 最后再考虑 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,否则体积会非常夸张。