视图 · 约 2 分钟
四目录各有分工
OverlayFS 是 Linux 内核提供的联合文件系统:把多个目录叠起来,呈现统一的文件路径。你已学过镜像与容器的关系;这里解释它们如何共享只读文件。[1]
lowerdir:下层目录。在这个容器的挂载中只读,可由多个容器共享。多个下层按优先级查找,同名普通文件取较上层的版本。upperdir:上层目录。存放此容器新增的文件、改写后的副本和删除标记。workdir:内核操作使用的工作目录。挂载前准备为空目录,并与 upperdir 位于同一文件系统。merged:挂载点,暴露合并视图。应用通过它读写,上下层目录由内核管理。[1][3]
“只读层”描述的是这个挂载怎样使用目录。并非宿主机上的每个底层目录都天然不可写。Docker 管理这些目录,你不应直接编辑它们。[1]
lowerdir 里有 /app/config = base,upperdir 里有同名文件 = custom。容器读到哪个?workdir 里偶然有同名文件会参与查找吗?
读到 upperdir 的 custom。workdir 不参与可见文件的合并。同名普通文件按层优先级选择,不会把两份文字拼接起来。
写入 · 约 3 分钟
首次写入先复制
写时复制(copy-on-write,CoW)让未改动的文件继续共享。首次改写下层已有文件时,先把它复制到 upperdir,这一步叫 copy_up;再修改上层副本。后续写入直接修改这份副本。[4]
- 只读文件时,上层没有就从下层读取,不需要为它创建上层副本。先猜:要把 base 改成 custom,会直接改下层吗?
- 应用通过 merged 请求写入。内核发现目标只在下层,需要准备上层文件。
- copy_up 把下层文件复制到上层;workdir 用于内核内部操作,下层内容保持不变。
- 在上层副本上写成 custom;合并视图优先展示上层文件。先猜:再次修改这个文件,还要从下层复制一次吗?
- 再次写入直接改上层副本,这里把 custom 改为 final。
- 新容器共享原镜像只读层,拥有独立可写层,所以仍能读到 base。
普通文件内容写入的复制粒度是文件。只改大文件的一小部分,首次写入也可能带来整文件复制开销;新建文件则直接落在 upperdir。这里不展示虚构的耗时或大小测量。[1]
两个容器共享同一镜像。A 第一次修改镜像里的 /app/config,B 没写过它。哪一项成立?
共享下层保持只读。想想第一次写入前的 copy_up。
只读文件无需复制;workdir 也不承载容器的完整文件视图。
A 有自己的 upperdir,修改的是 copy_up 后的副本。B 的上层没有这个改动,所以继续从共享的 lowerdir 读取原内容。
删除 · 约 2 分钟
删除用标记遮住下层
删除只在下层存在的文件时,内核在上层记录删除标记(whiteout)。合并视图遇到它,就隐藏同路径的下层文件,也不展示标记本身。下层原文件仍然存在。[3]
删除前
视图向下查找,找到镜像文件。
在 A 中删除后
标记拦住查找;不会退回下层。
同镜像新容器 B
A 的删除标记不属于 B。
为什么删掉 lowerdir 文件后,不能只让 upperdir 保持“没有这个文件”?请用查找顺序解释。
如果上层只有“没有”,查找会继续到下层,原文件就又可见了。whiteout 明确记录“这个路径已删除”,让查找停下来。
- 说出上层缺失时会继续查下层。
- 说出 whiteout 隐藏下层同名文件,下层本体没变。
深入:whiteout 是 .wh.文件名吗?
要区分挂载中的表示和镜像归档中的表示。Linux OverlayFS 可用设备号 0/0 的字符设备,或带 trusted.overlay.whiteout 扩展属性的零长度普通文件表示 whiteout。扩展属性是附在文件上的额外元数据。它不是供应用读取的普通空文件。[3]
开放容器倡议(Open Container Initiative,OCI)的镜像规范,在层的 tar 归档中使用 .wh.<原文件名> 记录删除。这是分发格式,不能据此断言宿主机 upperdir 一定有同名 .wh. 文件。[8]
目录还有 opaque(不透明)标记,可阻止合并同名下层目录的子项。本页实验只删普通文件,不把目录删除的所有情况混在一起。[3]
辨识 · 约 2 分钟
先认后端再读字段
overlay2 是 Docker 经典存储驱动名;overlayfs snapshotter 是 containerd 使用的快照后端名。containerd 是 Docker 使用的容器运行时,快照后端负责准备和管理容器的文件系统层。二者都能使用 Linux OverlayFS,管理方式和暴露字段不同。[2]
截至 2026-11-03,Docker Engine 29.x 的新安装默认使用 containerd 镜像存储,默认 snapshotter 是 overlayfs。从旧版升级通常保留经典驱动,直到主动切换。不要单凭版本猜后端。[2]
在连接 Docker 的终端运行。记录 Server 版本、Storage Driver 和 DriverStatus。下图是字段判读指南,不是本机输出。官方 containerd 文档给出的 driver-type 值是 io.containerd.snapshotter.v1。[2]
经典 overlay2
- Storage Driver 为
overlay2。 - 查看容器
GraphDriver。 Data中识别 LowerDir、UpperDir、WorkDir、MergedDir。
containerd / overlayfs
- DriverStatus 的 driver-type 指向 containerd snapshotter。
- 先查看容器完整 inspect JSON。
- 新 API 查
Storage.RootFS.Snapshot;不假定有四目录路径。
Engine 29 的容器 inspect 没有 GraphDriver,却有 Storage.RootFS.Snapshot。能据此认定容器没有可写层吗?你接下来查什么?
不能。先结合 docker info 判断存储后端。containerd 使用 snapshotter,Engine 29 在 API 1.52 及以上用 Storage 描述它;旧 API 兼容响应可能保留 GraphDriver 名称,却不提供经典驱动的路径。字段不同不能推翻分层机制。[7]
验证 · 约 5 分钟
实验对照原文件
编写环境没有 Docker。以下是待运行实验;预期现象按文件内容与官方机制推导,尚待维护者补充实测输出。没有展示虚构的版本、ID、大小或 inspect 路径。
在能访问 Docker Engine 的 Bash 等终端中执行,服务端须运行 Linux 容器。需要拉取 alpine:3.21;该标签不固定镜像摘要,实验不依赖其大小。实验会创建一个临时构建目录、本地镜像、构建缓存和一个无网络容器;不使用绑定挂载、端口发布或特权模式。需要本机 Docker 使用权限。
1. 准备可控的只读层
构建一个在 /opt/overlay-lab 放入两个普通文件的镜像。这个路径避开 Docker 单独挂载的 /etc/hosts 等文件。
overlay_lab_dir=$(mktemp -d)
cat > "$overlay_lab_dir/Dockerfile" <<'DOCKERFILE'
FROM alpine:3.21
RUN mkdir -p /opt/overlay-lab \
&& printf 'base\n' > /opt/overlay-lab/config.txt \
&& printf 'keep\n' > /opt/overlay-lab/obsolete.txt
DOCKERFILE
docker build -t overlayfs-lab:issue32 "$overlay_lab_dir"
预期镜像构建成功;输出随基础镜像和构建器变化,本页不预填构建日志。如果已有同名实验镜像或容器,请先换一个专用名称,并同步替换后面的命令。
2. 留一个容器供 inspect
docker run -d --network none --name overlayfs-lab-32 \
overlayfs-lab:issue32 sleep 3600
docker inspect --type container overlayfs-lab-32
先按上一节记录 docker info。完整 JSON 是所有后端的观察入口。仅当确认为经典 overlay2 且存在 GraphDriver,再执行下面的格式化命令。[5]
docker inspect --type container \
--format '{{json .GraphDriver}}' overlayfs-lab-32
| 字段 | 对应关系 | 你要核对的内容 |
|---|---|---|
LowerDir | lowerdir | 通常是冒号分隔的目录列表,可能包含 Docker 准备的初始化层。 |
UpperDir | upperdir | 当前容器的可写目录。 |
WorkDir | workdir | 内核工作目录,与上层在同一文件系统。 |
MergedDir | merged | 容器运行时的合并挂载点。 |
这些键名可在 Moby 的 overlay2 驱动源码核对。不要编辑返回路径下的文件。[10]
如果使用 containerd,跳过上面的 GraphDriver 命令。若完整 JSON 中有 Storage.RootFS.Snapshot,可执行:
docker inspect --type container \
--format '{{json .Storage.RootFS.Snapshot}}' overlayfs-lab-32
它描述 snapshotter,不保证提供四个路径。若客户端协商的是旧 API,保留完整 JSON 和 docker info 作为记录即可,不必强行读取不存在的字段。[7]
经典 overlay2 的 JSON 里,哪个 Data 键对应内核工作目录?
GraphDriver.Data.
WorkDir 对应 workdir;MergedDir 对应合并挂载点。只在实际输出支持这组字段时使用它们。
3. 在同一容器里改写和删除
先预测:修改 config.txt 并删除 obsolete.txt 后,A 中能读到什么?从同一镜像启动 B 又能读到什么?
A 读到 custom,且 obsolete.txt 不可见;新容器 B 仍读到 base 和 keep。A 的上层副本和删除标记不会修改镜像。
docker exec overlayfs-lab-32 sh -ec '
cat /opt/overlay-lab/config.txt
printf "custom\n" > /opt/overlay-lab/config.txt
rm /opt/overlay-lab/obsolete.txt
cat /opt/overlay-lab/config.txt
test ! -e /opt/overlay-lab/obsolete.txt && printf "hidden\n"
'
docker diff overlayfs-lab-32
docker run --rm --network none overlayfs-lab:issue32 sh -ec '
cat /opt/overlay-lab/config.txt
cat /opt/overlay-lab/obsolete.txt
'
- 预期,未实测:exec 中三次打印依次为
base、custom、hidden,每项一行。这是脚本的确定性期望,不是终端记录。 - 预期,未实测:diff 中 config.txt 应标为
C(修改),obsolete.txt 应标为D(删除)。目录本身也可能有变化项,不假定行数或顺序。[6] - 预期,未实测:新容器打印
base和keep,各一行,然后由--rm自动删除。
docker diff 展示容器文件系统相对镜像的变化。它不直接展示磁盘上的 whiteout 编码。这组实验验证“当前视图已删除、镜像内容仍在”;whiteout 的具体表示以内核文档为依据。[6][3]
4. 清理本次实验
docker rm -f overlayfs-lab-32
docker image rm overlayfs-lab:issue32
rm "$overlay_lab_dir/Dockerfile"
rmdir "$overlay_lab_dir"
仅移除上述专用容器、镜像标签和临时目录。拉取的基础镜像及构建缓存可能保留;本页不要求全局清理。即使实验中途停止,也可用这段命令收尾。
迁移 · 约 1 分钟
遮住不等于回收
如果在后续 Dockerfile 层删除早先层的大文件,后续视图会隐藏它,早先层仍携带这些数据。whiteout 不能让下层文件占用的空间消失。避免把只在构建期间使用的大文件留进较早的最终镜像层。[4][8]
- lowerdir 提供共享只读内容,upperdir 保存当前容器的变化。
- merged 是应用所见;workdir 为内核工作,不参与内容合并。
- 首次改写先 copy_up;删除下层文件用 whiteout 遮住它。
- 先查后端,再选 inspect 字段;容器可见变化不等于镜像被改写。
镜像较早层包含大文件 /archive.bin,后续层删除了它。最终容器看不到该文件,能据此认定早先层占用的空间已回收吗?
共享下层不会被后续层的 rm 改写;变化由上面的层记录。
workdir 不是回收站。whiteout 改变可见性,不会搬走下层文件。
删除标记让合并视图跳过下层路径,但不会改写已存在的镜像层。要减少这类数据,需改变构建方式,使临时文件不留在最终镜像的早期层;判断实际占用还要测量,不能只看容器内 ls。
来源
核对日期:2026-11-03。Docker 行为以官方文档为准;GraphDriver 的兼容细节另核对 Engine 29.1.3 对应的 Moby 源码,不代表本页在该版本实测过。
- Docker:OverlayFS storage driver,四目录、读写、copy_up 和 whiteout;该文档讲经典 overlay2。
- Docker:containerd image store,29.x 新安装默认值、升级行为、DriverStatus 与默认 snapshotter。
- Linux 内核:Overlay Filesystem,Directories、whiteouts and opaque directories、Multiple lower layers。
- Docker:Storage drivers,镜像共享、容器可写层、copy-on-write 与数据卷边界。
- Docker CLI:docker inspect,完整 JSON 与 --format。
- Docker CLI:docker container diff,A/C/D 的含义。
- Moby 29.1.3:容器 inspect 生成及API 兼容转换,snapshotter 使用 Storage,API < 1.52 恢复兼容 GraphDriver。
- OCI Image Spec v1.1.1:Whiteouts,镜像 tar 层中的 .wh. 格式。
- Docker Desktop for Mac FAQ,容器与镜像数据位于虚拟机的磁盘映像。
- Moby 29.1.3:overlay2 GetMetadata,LowerDir、UpperDir、WorkDir、MergedDir 的字段来源。