docker镜像由多层只读目录按序堆叠构成,依赖unionfs实现统一视图;每层为独立目录(如/var/lib/docker/overlay2/),含diff/、lower-id等元数据,通过写时复制、文件隐藏和自上而下查找机制叠加生效,镜像id仅标识顶层层,多镜像可共享中间层,保障复用与一致性。

Docker镜像在文件系统中不是单个打包文件,而是一组按顺序堆叠的只读目录层,底层依赖联合文件系统(UnionFS)实现“看起来像一个整体”的视图。理解这一点,关键在于看清分层结构如何映射到实际存储、叠加逻辑如何生效,以及各层在磁盘上真实存在的方式。
镜像层对应真实的目录结构
Docker守护进程将每层镜像保存为宿主机上的独立目录(通常位于 /var/lib/docker/overlay2/ 或类似路径下),每个目录包含该层的全部文件变更(新增、修改、删除)。这些目录本身不可写,内容固定;它们不合并,也不复制,只是被记录和引用。
- 每一层都有唯一ID(如 sha256:abc123...),对应一个子目录,里面是 diff/、link、lower-id 等元数据文件
- diff/ 存放本层独有的文件和目录(比如 RUN apt-get install vim 新增的二进制和配置)
- lower-id 指向它直接依赖的下一层ID,形成明确的父子链
- 镜像ID 实际是顶层层ID的摘要,不代表整个镜像的完整快照
UnionFS叠加机制决定“看到什么”
当启动容器时,Docker会在镜像最上层再挂载一个可写层(container layer),然后通过 UnionFS 将所有只读层 + 可写层按顺序叠加成一个统一的文件系统视图。叠加规则简单但关键:
- 文件查找从上到下:先查可写层 → 再逐层查只读层,找到第一个即返回
- 文件隐藏:若某层删除了底层存在的文件(如 rm /bin/bash),UnionFS 会在可写层生成一个 .wh.bin/bash 白名单标记,使该文件对上层不可见
- 写时复制(Copy-on-Write):修改只读层中的文件时,会先将其完整拷贝到可写层再编辑,原层内容保持不变
镜像ID与文件系统层不是一一映射关系
一个镜像ID可能由多个层组成,但多个镜像可以共享中间层。例如 ubuntu:22.04 和 nginx:alpine 都基于相同的 base layer(如 scratch 或 busybox 的某层),它们在磁盘上只存一份物理副本,通过层ID被不同镜像引用。
- 执行 docker history
可看到每一层的大小、创建命令和是否复用(显示 “Missing” 或 “—” 表示未复用) - 执行 docker image inspect
查看 RootFS.Layers 字段,列出所有层ID数组,顺序即叠加顺序 - 相同层ID在不同机器上解压后目录结构完全一致,保障跨环境一致性
为什么不能直接修改镜像层?
因为所有镜像层默认是只读的——这是分层设计的安全前提。你无法 touch /etc/hosts 或删除 /usr/bin/python3,哪怕进入容器执行这些操作,实际也只影响最上层的可写空间。一旦 commit 成新镜像,Docker 仅把可写层中真正变化的文件打包为一个新层,追加到原有层栈顶部。
- commit 不会重写旧层,也不会压缩或去重已存在的文件
- 反复 commit 容易产生冗余层(如多次安装又卸载同一包),导致镜像臃肿
- 推荐用 Dockerfile 构建替代 commit,利用构建缓存跳过未变层,更可控、可追溯











