Docker镜像本质是多个只读层叠加的联合文件系统视图,由overlay2等UnionFS驱动实现;容器运行时在其上添加可写层,通过写时复制(CoW)和whiteout机制统一挂载为完整文件系统。
理解 docker 镜像,核心不是把它当成一个“打包好的压缩包”,而是看作一套由只读层叠加而成的文件系统视图——这个叠加能力,就来自联合文件系统(unionfs),尤其是现代 docker 默认使用的 overlay2 驱动。
镜像 = 多个只读层的有序叠加
Docker 镜像每一层都对应 Dockerfile 中一条指令(如 FROM、RUN apt-get install、COPY app.py /app/)所产生的文件差异集合。它不存完整系统,只存“比上一层多了什么、改了什么、删了什么”。这些层按顺序堆叠,最底层通常是 bootfs(含内核引导),往上是发行版 rootfs(如 Debian 或 Alpine 的基础文件),再往上是应用相关层。
- 所有层默认只读,不可修改
- 相同哈希值的层(比如两个镜像都基于
ubuntu:22.04)在磁盘上只存一份物理数据,被多个镜像共享 - 执行
docker image history <image>可直观看到每层大小和构建命令,验证“层即变更” - 执行
docker inspect <image> --format='{{.RootFS.Layers}}'能直接列出各层 SHA256 哈希,确认是否复用已有层
容器运行时:顶部加一层可写层
当你 docker run 启动一个容器,Docker 会在镜像所有只读层之上,动态添加一个空的可写层(upperdir),再通过 overlay2 将它们合并挂载为一个统一路径(merged),这就是容器里看到的 /。
- 读文件:从上往下查,先找可写层,没找到就逐层向下,直到命中第一个匹配项
- 写文件:如果修改的是只读层里的文件(如
/etc/nginx.conf),UnionFS 自动触发 写时复制(CoW)——先把原文件完整复制到可写层,再编辑副本;原始只读层丝毫不动 - 删文件:不能真删底层数据(可能被其他容器共用),而是在可写层生成一个
.wh.filenamewhiteout 文件,用于“屏蔽”下层同名项
为什么必须用联合文件系统?
没有 UnionFS,就无法实现 Docker 的关键特性:
- 空间高效:10 个基于同一基础镜像的容器,底层只读层共用一份,节省大量磁盘
- 启动极快:无需解压或拷贝整个文件系统,只需挂载已有层 + 新建轻量 upperdir
- 构建缓存可靠:只要某层哈希不变,Docker 就跳过执行,直接复用——这依赖层内容的确定性哈希
- 镜像不可变:你 commit 容器生成新镜像,本质是把当前可写层固化为一个新的只读层,原镜像层依然完好无损
一个典型误区:删文件 ≠ 释放空间
在容器里 rm -rf /var/log/*.log,宿主机磁盘使用量往往不降反升。原因正是 whiteout 机制:删除操作在可写层留下标记,而非清理底层数据。若随后 docker commit,这些 whiteout 会变成新镜像的一部分,导致体积膨胀。真正瘦身应靠优化 Dockerfile(如合并 RUN 指令、用 --no-install-recommends)、多阶段构建,而非运行时清理。


















