写时复制(cow)是“动了才搬”而非提前备份:镜像由只读增量层堆叠而成,容器启动时添加空可写层,仅在修改、删除或新建文件时触发对应操作——改删文件需复制整份或创建whiteout,新建则直写可写层,所有只读层保持不变且被多容器共享。

写时复制(Copy-on-Write,CoW)不是“提前备份”,而是“动了才搬”。Docker 镜像本身由多个只读层堆叠而成,容器运行时在它们之上加一层空的可写层;所有修改操作——包括改文件、删文件、新建文件——都只发生在这一层,且只有真正要改某个只读层里的文件时,才会把它整个复制上来再操作。
只读层:镜像的静态基石
每个镜像层对应 Dockerfile 中一条产生文件变更的指令(如 RUN、COPY、ADD),只保存与上一层的差异,不重复存储内容。这些层按顺序堆叠,全部只读、不可修改,通过内容哈希(如 sha256)唯一标识,天然不可变。
- FROM ubuntu:22.04 → 提供基础系统文件,多个底层只读层组成
- RUN apt install nginx → 新增一层,仅含 nginx 及其依赖路径和二进制
- COPY app.conf /etc/nginx/ → 再新增一层,只存这个配置文件
可写层:容器专属的动态顶层
执行 docker run 时,Docker 不复制整个镜像,而是在所有只读层之上挂载一个空的可写层(UpperDir)。它初始为零大小,随写入逐步增长,且完全独立于其他容器。
- 新建文件(如
echo "ok" > /tmp/test.txt)→ 直接写入可写层,不触发 CoW - 修改已有文件(如
sed -i 's/a/b/' /etc/nginx.conf)→ 先从某只读层完整复制该文件到可写层,再编辑副本 - 删除文件(如
rm /usr/bin/python3)→ 在可写层创建.wh.python3白名单文件,屏蔽下层同名项,原文件仍保留在只读层中
CoW 的实际影响与常见误区
CoW 按文件粒度工作,不是按块或字节,也不做增量同步。这意味着大文件哪怕只改一行,也要整份复制上来;而频繁删除日志等操作,反而会让可写层迅速膨胀——因为每个被删文件都先被复制一次,再标记隐藏。
- 读取未修改文件(如
cat /lib/x86_64-linux-gnu/libc.so.6)→ 直接命中最底层,零开销 - 反复追加日志(
echo "log" >> /var/log/app.log)→ 日志文件始终在可写层新建或追加,不触发 CoW - 多个容器同时运行同一镜像 → 各自拥有独立可写层,互不影响,但共享全部只读层
为什么构建顺序会影响镜像体积和缓存效率
CoW 的分层逻辑直接决定构建缓存是否生效。只要某一层内容变了,它上面所有层都会失效重建。例如把源码 COPY . /app 放在 RUN npm install 前面,哪怕只改了一个 README.md,也会导致 node_modules 层重新安装。
- 推荐做法:把变动少的内容放前面(如
COPY package*.json→RUN npm install) - 再放变动频繁的内容(如
COPY . .)→ 上层缓存稳定,构建更快,生成镜像更小











