Docker镜像是由多个只读层叠加构成的轻量级软件包,每层仅记录差异,通过OverlayFS在运行时动态挂载为统一视图;容器启动时添加可写层,采用Copy-on-Write机制实现高效隔离与复用。

直接记住一句话:Docker 镜像不是一整块“硬盘镜像”,而是一叠透明胶片——每张胶片只记录差异,叠加后看到完整画面,但谁都不能改底下的胶片。
只读层本质是“快照差异包”
每一层对应 Dockerfile 中一条指令(比如 RUN apt-get install curl 或 COPY app.py /app/),它不保存整个文件系统,只存这次操作新增、修改或删除的文件变化。这些层按顺序堆叠,越靠上的层优先级越高。物理上它们各自独立存放在 /var/lib/docker/overlay2/diff/ 下,互不覆盖。
- 基础层(如 alpine:3.18)只含最小操作系统文件,约 5MB
- 软件层(如安装 nginx)只存二进制和依赖,不重复打包基础文件
- 配置层(如 COPY nginx.conf)仅含那个配置文件,其他全复用前两层
联合文件系统是“动态视图引擎”
OverlayFS 不合并磁盘数据,而是在内核里挂载出一个逻辑视图:lowerdir(多个只读层路径,冒号分隔)、upperdir(容器可写层)、merged(你看到的 / 根目录)。所有读写操作都走这个 merged 路径,背后由内核实时决定从哪一层取或写。
- 读 /etc/hosts?先查 upperdir → 没有 → 再按 lowerdir 从右往左找(即从最上层镜像层开始往下扫)→ 找到就返回
- 改 /etc/nginx/nginx.conf?先从 lowerdir 复制该文件到 upperdir(Copy-on-Write),再编辑副本,原镜像层毫发无损
- 删 /tmp/log.txt?upperdir 里建个 whiteout 文件(.wh.log.txt),遮住 lowerdir 同名文件,实现“逻辑删除”
分层 ≠ 分割,叠加 ≠ 复制
镜像层之间没有边界隔离,它们共享同一套路径命名空间。你执行 docker image history nginx:latest 看到的每一行,就是一个 layer ID 和它的构建命令,大小列的是该层新增内容体积,不是整层大小——因为大量文件来自下层复用。
- 两个基于 ubuntu 的镜像,哪怕内容不同,只要共用 base 层,磁盘只存一份 base
- 启动 10 个 nginx 容器,共用同一套只读层,每个只多占几 MB 可写层空间
- docker commit 生成新镜像时,只打包 upperdir 的变更,不复制整个文件系统
容器启动那一刻才真正“组装”视图
镜像本身只是静态文件集合,不运行、不挂载。当你执行 docker run,Docker daemon 才调用内核的 overlay mount 接口,把指定镜像的所有 lowerdir + 新建 upperdir 挂到 merged 目录,此时容器进程才能看到完整的根文件系统。这个过程毫秒级完成,且全程无文件拷贝。
- 没运行的镜像 = 一堆带元数据的目录 + JSON 配置
- 运行中的容器 = 一个挂载点 + 一组命名空间 + 一个 PID 进程
- 所有“镜像变容器”的魔法,都在那一行 mount 命令里


















