← 全部笔记

Docker · 4 存储与数据卷

容器可写层为什么会丢数据

写进可写层的文件跟着容器走。删除容器会删除这份变化,镜像不会替你保存它。

约 15 分钟 · 截至 2026-11-03,Docker Engine 29.x · 实验未实测

图 1 示意图:删除容器 A 会丢掉 A 的可写层,底下的镜像层不变;框的高度不代表容量。

文件归谁

改动属于容器

镜像只读层提供初始文件。容器可写层(writable layer)记录本容器新增、修改和删除的文件。这里先看没有挂载的普通路径;挂载是把另一处存储接到容器内某个路径,后面会做对照。依据:Storage。

容器里看到的是这些层合成的文件视图。新建文件进入可写层;修改镜像里的文件,也只把变化留在当前容器。删除镜像里的文件不会改写镜像,只让它在这个容器里不可见。你可以回看镜像、容器、仓库的关系。

A 和 B 来自同一镜像,都没有挂载。A 新建 /lab/note.txt 后退出。此时 B 能看到它吗?删除 A 后再从同一镜像创建 C,C 能看到吗?请分别写理由。

B 和 C 都看不到。文件只写进 A 的可写层。B 有自己的可写层,C 创建时也会得到新的一层;镜像里没有这个文件。A 退出时文件仍留在 A 内,删除 A 才删除这份变化。

深入一点:写时复制与 Docker 29 的存储后端

写时复制(copy-on-write)让容器先共享镜像文件;需要修改下层文件时,再把它复制到自己的可写层修改。删除下层文件会记录遮蔽信息,底层原件保留。具体文件布局由存储后端决定。依据:Storage drivers。

Docker Engine 29.0 起,全新安装默认用 containerd image store。它通过 snapshotter(管理文件系统快照的组件)存储镜像和容器数据;升级安装可能仍使用原有存储驱动。本页画的是文件归属,不能据此认定你的主机目录一定是 /var/lib/docker/overlay2。依据:containerd image store。

跟踪同一份变化

停止仍保留

默认情况下,停止容器只结束进程,可写层仍在。docker start 启动原容器,docker run 创建新容器。只有前者继续使用原来的可写层。依据:docker run。

从创建到删除的六步记录 A 写入文件后退出,再启动 A 时文件仍在。删除 A 后原可写层消失。重新运行同一镜像得到 B 的新可写层,里面没有 A 的文件。时间自上向下,各行是当时的快照。 时间向下 · 每行是当时的快照 run → A:新的可写层 写入 → A:有 note.txt 退出 → A:文件仍在 start → A:读到原文件 rm → A:可写层已删除 run → B:没有 A 的文件 同一镜像:六步中始终未改变
  1. 用 run 创建 A,得到 A 自己的可写层。
  2. A 把 note.txt 写进可写层。先猜:进程退出后,这层还在吗?
  3. A 的进程退出;没有 --rm 时,容器与可写层仍保留。
  4. 用 start 启动 A,可以读取之前的 note.txt;读完再次退出。先猜:删除 A 后,用同一镜像能找回文件吗?
  5. 用 rm 删除已停止的 A,A 的可写层也被删除。
  6. 再次 run 创建 B,只带上镜像的初始文件;A 的变化不会进入 B。
图 2 停止与启动可以延续同一容器的数据;删除后再创建,得到的是新的可写层。

--rm 表示容器退出时自动删除。因此,带它运行的容器即使没有手动执行 docker rm,退出后可写层也会消失。依据:Clean up。

没有挂载,也没加 --rm。A 写入文件后停止。哪一个操作会继续使用 A 的可写层?

run 会新建容器。相同镜像只保证相同的初始文件。

名字可以复用,但删除操作已经删除旧可写层。

start 启动的是现有的 A,因此保留它的文件变化。run 总会创建新容器;同名、同镜像都不会自动恢复已删除的可写层。

在你的机器上验证

删除前先查改动

未实测

编写环境没有 Docker CLI。下面给的是可复制命令与预期现象,没有实测终端输出,待维护者补测。命令在宿主机的 POSIX shell 中按顺序执行,需要能访问运行 Linux 容器的 Docker Engine。

实验可能下载 alpine:3.21 并占用本地磁盘。容器均设 --network none,不发布端口;不挂载宿主机目录。容器名 wl21-demo 必须未被占用;冲突时整组换名,不要删除已有业务容器。

① 创建文件,让进程退出

脚本第一次创建文件;若再次启动同一容器,则读取已有文件。主实验刻意不用 --rm,以便退出后检查。

docker run --name wl21-demo --network none alpine:3.21 sh -c '
  mkdir -p /lab
  if [ -f /lab/note.txt ]; then
    cat /lab/note.txt
  else
    printf "first run\n"
    printf "hello layer\n" > /lab/note.txt
  fi
'

预期(未实测):脚本走 else 分支,打印代码中的 first run,将 hello layer 写入文件后退出。容器此时已停止。

② 用 diff 查可写层

docker diff 列出容器创建以来发生变化的文件或目录路径。它显示改动类型,不显示文件内容。依据:docker container diff。

docker diff wl21-demo

预期(未实测):关注新建的 /lab 与 /lab/note.txt,应归为新增。实际条目、顺序和额外系统路径以你的环境为准,不能把整个列表当成固定输出。

A · Added
原先没有 → 新增文件或目录。
C · Changed
原先存在 → 内容或元数据发生变化。
D · Deleted
原先可见 → 在当前容器中被删除。
图 3 diff 的字母表示路径的变化类型;C 也可能出现在目录上,不能仅凭它判断文件内容改了什么。

假设你手动删除一个镜像自带的普通文件,没有挂载。diff 对这个文件记录哪一个字母?

变化类型:

D 表示删除。该文件在当前容器的合成视图中不可见,镜像只读层中的原件仍然存在。新建容器仍从原始镜像开始。

③ 启动原容器,再删除

docker start -a wl21-demo
docker rm wl21-demo

预期(未实测):start -a 启动原容器并连接其输出;脚本读到原文件,打印文件里保存的 hello layer。它退出后,rm 删除容器与可写层;镜像仍在。依据:docker rm。

④ 同一镜像,新容器

docker run --rm --network none alpine:3.21 sh -c '
  if [ -e /lab/note.txt ]; then
    cat /lab/note.txt
  else
    printf "no note in new container\n"
  fi
'

预期(未实测):新容器走 else 分支,打印代码中的 no note in new container。这是新的可写层。退出后,--rm 自动删除它。

深入一点:diff 是备份吗?

diff 只有改动列表,不能恢复文件,也不展示文件内的逐行差异。不要用它判断挂载数据是否已备份。若重要文件仍在旧容器里,先保留旧容器并导出需要的数据,再处理重建;不要先 rm 再找文件。

把数据的寿命延长

数据卷独立保留

数据卷(volume)是由 Docker 管理、独立于容器生命周期的存储。把它挂载到 /data 后,写入这个路径的数据进入卷,其他普通路径仍写入可写层。删除容器后,再把同一个卷挂到新容器,就能继续读取数据。依据:A volume's lifecycle。

普通路径 /lab
容器 A → A 的可写层
删除 A:文件一起删除

新容器 B 的可写层从头开始。

挂载路径 /data
容器 A → 写入 /data
↓ 写到容器生命周期之外
命名卷 wl21-data
note.txt 保留

删除 A 后,B 挂载同一卷读取。

图 4 卷改变的是目标路径的数据归属;它不会自动搬走容器中其他路径已有的文件。

应用同时写 /data/orders 和 /tmp/cache,只有 /data 挂载了命名卷。删除并重建容器后,怎样继续读取订单?缓存会怎样?请说出两个路径的归属。

重建时挂载同一个卷到 /data,订单仍在卷里。/tmp/cache 写在旧容器的可写层中,会随旧容器删除;新容器不会自动继承它。

  • 指出要复用同一个卷
  • 区分 /data 与 /tmp 的存储归属
  • 说明卷不等于全容器备份

可选对照:容器自动删,命名卷保留

以下仍未实测。先确认没有名为 wl21-data 的已有卷,再执行。type=volume 指定卷,src 是卷名,dst 是容器内挂载位置。

docker volume create wl21-data
docker run --rm --network none --mount type=volume,src=wl21-data,dst=/data alpine:3.21 sh -c 'printf "hello volume\n" > /data/note.txt'
docker run --rm --network none --mount type=volume,src=wl21-data,dst=/data alpine:3.21 cat /data/note.txt

预期(未实测):第一个容器退出并被自动删除后,第二个容器仍能读到代码写入卷的 hello volume。这里使用命名卷,--rm 不会删除它;匿名卷的清理规则不同,留到下一页。

清理实验数据

以下命令会删除本实验卷及其全部内容。确认只用于此次实验,再执行。主实验的两个容器已在前面删除。

docker volume rm wl21-data

数据卷也会占用宿主机磁盘,手动删卷、应用误删、磁盘故障仍会丢数据。持久化不等于备份。下一页再比较卷、绑定挂载与内存临时挂载;本页先记住:需要跨容器保留的数据,应明确选择独立存储。

离开前检索一次

按归属判断

旧容器把订单写到没有挂载的 /orders。你删除它后,用相同镜像、相同名字重建。哪项判断正确?

写入可写层不会修改镜像。

名字只是容器的标识,复用名字不会找回已删除的层。

删除旧容器也删除了订单所在的可写层。新建容器时再挂一个空卷,不能恢复已删除的数据。应事先把订单路径挂载到持久存储,并另做备份。

来源

核对日期:2026-11-03;说明范围:Docker Engine 29.x、Linux 容器。实验未实测,页面不记录本环境不存在的 Engine 版本或镜像大小。

  1. Storage · Container layer basics:只读层、可写层与挂载。
  2. Storage drivers · Containers and layers / Copy-on-write:文件变化与镜像层的关系。
  3. containerd image store:29.0 起全新安装的默认后端。
  4. docker run:新建容器、start 保留变化、--rm 清理规则。
  5. docker container diff:A / C / D 的官方定义。
  6. docker container rm:容器删除。
  7. Volumes:卷生命周期、--mount 语法、命名卷与匿名卷。