Docker diff 详解:看清容器里到底改了什么

Docker diff 详解:看清容器里到底改了什么

简介

容器启动后,应用可能会创建日志、修改配置、生成 PID 文件,也可能把上传文件写进容器内部。docker diff 专门用来查看这些变化:列出容器创建以来,相对于原始镜像发生变化的文件和目录,并用 A、C、D 标记变化类型。

这条命令适合排查:

  • 容器运行后新增了哪些文件
  • 应用是否修改了镜像中的配置
  • 日志、缓存、临时文件是否写进了容器可写层
  • 删除容器前是否还有重要文件没有迁移到卷中
  • docker commit 前容器里到底有哪些运行时改动

基本语法

shell 复制代码
docker diff CONTAINER

完整写法:

shell 复制代码
docker container diff CONTAINER

docker diff 是 docker container diff 的简写形式。CONTAINER 可以填写容器名称、短 ID 或完整 ID:

shell 复制代码
docker diff nginx-test
docker diff 7d4f6b123abc

容器处于运行中或已停止状态,都可以执行 docker diff。比较基准是容器创建时使用的镜像,容器重启不会清空这些变化。

输出中的 A、C、D

text 复制代码
A /app/config/local.json
C /etc/nginx/nginx.conf
D /tmp/old-cache
  • A(Added):新增了文件或目录。
  • C(Changed):文件或目录发生了变化,可能是内容、权限、属主或其他元数据变化。
  • D(Deleted):删除了原来存在于镜像中的文件或目录。

C 不代表内容一定发生变化。例如,只修改文件权限,也可能出现 C。

第一个实战:创建、修改、删除文件

下面使用体积很小的 Alpine 镜像演示,不依赖 Nginx、PHP 等额外服务。

1. 启动测试容器
shell 复制代码
docker run -d \
  --name diff-demo \
  alpine:3.24 \
  sh -c 'sleep 3600'

这里的 alpine 是 Docker Hub 上的官方镜像,3.24 是镜像标签,不是本地自定义名称。首次执行时,如果本机没有这个镜像,Docker 会自动拉取;也可以提前执行 docker pull alpine:3.24。

sh -c 'sleep 3600' 的作用是让容器内的主进程持续运行 3600 秒,也就是 1 小时。容器的生命周期通常跟主进程绑定,sleep 结束后主进程退出,容器也会变成 exited 状态。这个演示只需要一个持续存在的容器,不需要启动真实服务。

查看初始状态:

shell 复制代码
docker diff diff-demo

不同 Docker 版本、存储驱动和镜像启动方式,初始输出可能不同。有些运行时目录或设备文件可能已经显示为 C,不必把这些系统条目直接当成业务文件。

2. 创建一个文件
shell 复制代码
docker exec diff-demo sh -c \
  'printf "debug=true\\n" > /app-settings.conf'

docker diff diff-demo

可能看到:

text 复制代码
A /app-settings.conf

这个文件是容器运行后新增的,所以标记为 A。

3. 修改镜像中已有的文件

/etc/alpine-release 是 Alpine 镜像中的已有文件。下面只修改权限,不改文件内容:

shell 复制代码
docker exec diff-demo chmod 600 /etc/alpine-release
docker diff diff-demo | grep '/etc/alpine-release'

输出类似:

text 复制代码
C /etc/alpine-release

这个例子说明,C 不只表示文本内容变更,权限、属主等元数据变化也可能触发 C。

4. 删除镜像中已有的文件
shell 复制代码
docker exec diff-demo rm /etc/alpine-release
docker diff diff-demo | grep '/etc/alpine-release'

可能看到:

text 复制代码
D /etc/alpine-release

D 表示这个路径在原始镜像中存在,但在容器当前的文件系统视图中已经被删除。

如果某个文件是在容器启动后新增,随后又被删除,最终结果不一定保留 A 或 D 记录,因为当前状态已经回到了"没有这个文件"的样子。docker diff 关注的是最终差异,不是完整的操作日志。

清理容器:

shell 复制代码
docker rm -f diff-demo

删除容器会同时删除容器的可写层。没有写入卷或宿主机目录的数据,也会随之失去。

第二个实战:观察 Nginx 的运行时文件

shell 复制代码
docker run -d --name diff-nginx nginx:alpine
docker diff diff-nginx

输出可能包含:

text 复制代码
A /run/nginx.pid
A /var/log/nginx/access.log
A /var/log/nginx/error.log
C /var/lib/nginx/tmp

实际输出会受到镜像版本、启动脚本和 Docker 运行环境影响。常见变化包括:

  • /run/nginx.pid:保存 Nginx 主进程 ID 的文件。
  • /var/log/nginx/*.log:访问日志和错误日志。
  • /var/lib/nginx/tmp:请求处理过程中使用的临时目录。

这类输出可以回答一个实际问题:应用运行时把数据写到了哪里?如果日志持续写进容器可写层,可写层就会不断变大;如果日志本来应该持久化或集中采集,就需要重新设计日志方案。

shell 复制代码
docker rm -f diff-nginx

第三个实战:为什么挂载卷里的文件看不到

容器文件系统可以简单理解为几层叠加:

text 复制代码
镜像只读层
    + 容器可写层
    + Volume / Bind Mount
    = 容器内看到的文件系统

默认情况下,容器中新建的文件会进入容器可写层,docker diff 可以观察到这类变化。挂载卷和绑定挂载属于独立存储,挂载路径下的内容不属于容器可写层,不能依靠 docker diff 审计其中每个文件的变化。

使用命名卷演示
shell 复制代码
docker volume create diff-volume

docker run -d \
  --name diff-volume-demo \
  --mount type=volume,src=diff-volume,dst=/data \
  alpine:3.24 \
  sh -c 'sleep 3600'

docker exec diff-volume-demo sh -c \
  'printf "stored in volume\\n" > /data/result.txt'

docker diff diff-volume-demo

/data/result.txt 不会像普通容器层文件一样出现在差异清单中。原因不是文件没有变化,而是文件写入了 diff-volume,不在容器可写层内。

shell 复制代码
docker exec diff-volume-demo cat /data/result.txt
docker volume inspect diff-volume

docker rm -f diff-volume-demo
docker volume rm diff-volume

绑定挂载规则相同:

shell 复制代码
mkdir -p ./diff-bind

docker run -d \
  --name diff-bind-demo \
  --mount type=bind,src="$(pwd)/diff-bind",dst=/data \
  alpine:3.24 \
  sh -c 'sleep 3600'

docker exec diff-bind-demo sh -c \
  'printf "stored on host\\n" > /data/result.txt'

cat ./diff-bind/result.txt
docker diff diff-bind-demo

result.txt 实际写入宿主机的 ./diff-bind 目录。检查绑定挂载的变化时,直接检查宿主机目录;检查卷的变化时,在容器内查看挂载目录或使用卷自身的管理方式。

过滤输出

docker diff 本身没有按路径、状态或文件类型过滤的参数。输出较多时,可以交给 grep、awk 等命令处理。

shell 复制代码
# 只查看新增内容
docker diff CONTAINER | grep '^A '

# 只查看修改内容
docker diff CONTAINER | grep '^C '

# 只查看删除内容
docker diff CONTAINER | grep '^D '

# 只查看日志路径
docker diff CONTAINER | grep '/var/log'

# 只查看应用目录
docker diff CONTAINER | grep '^. /app/'

这里的 ^. 表示匹配开头的状态字母,后面跟一个空格。

配合其他命令排查磁盘占用

docker diff 只能列出路径,不能告诉文件占用了多少空间,也不会显示文件具体改了哪几行。排查容器磁盘变大时,可以配合以下命令:

shell 复制代码
# 先看容器可写层大小
docker ps --size
docker inspect --size CONTAINER

# 再看哪些路径发生了变化
docker diff CONTAINER

# 进入容器查看目录占用
docker exec -it CONTAINER sh
du -sh /var/log/* 2>/dev/null
du -sh /var/lib/nginx/* 2>/dev/null
exit

排查结果可以大致分成几类:

  • docker ps --size 很大,docker diff 中日志和临时文件很多:应用把运行时数据写入了容器可写层。
  • docker diff 变化不多,但业务数据很多:数据可能写进了 Volume 或 Bind Mount。
  • 镜像占用空间很大,但容器可写层很小:问题更可能出在镜像层或构建缓存,docker diff 无法替代镜像分析工具。

和 docker commit 配合使用

shell 复制代码
docker diff CONTAINER
docker commit CONTAINER demo-image:debug
docker history demo-image:debug

docker diff 适合在提交前确认变化范围,但不建议把长期维护的镜像构建流程依赖在手工修改容器和 docker commit 上。可复现的做法是把安装步骤、配置和文件复制写进 Dockerfile,再通过 docker build 生成镜像。

提交容器时,挂载卷中的数据不会被包含到新镜像里。因此,docker diff 看不到的卷数据,也不会因为 docker commit 自动进入镜像。

和其他 Docker 命令的区别

命令 主要用途 关注对象
docker diff 查看容器创建以来的文件系统变化 容器可写层
docker inspect 查看容器配置、挂载、网络和状态 容器元数据
docker inspect --size 查看大小信息 可写层大小等
docker history 查看镜像构建历史 镜像层
docker ps --size 查看容器大小 容器可写层和虚拟大小
docker exec 在容器中执行命令 容器内部

确认某个路径是否为挂载目录:

shell 复制代码
docker inspect -f '{{json .Mounts}}' CONTAINER

查看文件的具体内容差异时,把文件复制到宿主机,再配合普通的 diff:

shell 复制代码
docker cp CONTAINER:/etc/app.conf ./app.conf.container
diff -u ./app.conf.image ./app.conf.container

docker diff 负责回答"哪些路径变了",普通 diff 才负责回答"文本具体改了什么"。

常见误区

误区一:把 docker diff 当成操作日志

docker diff 展示的是当前状态与镜像之间的差异,不会记录每一次写入、重命名或删除操作的时间顺序。需要知道修改者和修改时间时,应使用应用日志、审计系统或文件监控工具。

误区二:认为重启容器会清空差异

重启只会重新启动同一个容器,容器可写层仍然保留。只有删除容器并重新创建,原来的可写层才会消失。

误区三:认为 C /var/log 就代表只有目录本身变了

目录条目可能因为目录元数据变化、子项增删或运行时行为出现在结果中。需要继续查看目录下的文件,或者进入容器检查内容和时间。

误区四:用容器可写层保存数据库和上传文件

容器可写层适合临时数据,不适合数据库文件、用户上传文件和大量日志。容器删除后,这些数据可能一起丢失;大量写入还会让可写层膨胀。

数据库、上传目录和需要跨容器共享的数据,应使用 Volume、Bind Mount 或外部存储。临时缓存则可以根据生命周期选择容器层或 tmpfs。

误区五:用 docker diff 对比两个镜像

docker diff 的对象必须是容器,不能直接填写两个镜像名。镜像层分析可以使用 docker history,需要逐层查看文件变化时可以使用专门的镜像分析工具。

一套实用排查流程

shell 复制代码
# 1. 找出可写层较大的容器
docker ps --size

# 2. 查看容器有哪些路径变化
docker diff CONTAINER

# 3. 重点筛选日志、缓存、临时目录
docker diff CONTAINER | grep -E '^. /(var/log|tmp|var/tmp|app)'

# 4. 查看挂载配置,排除 Volume 和 Bind Mount
docker inspect -f '{{json .Mounts}}' CONTAINER

# 5. 进入容器确认目录实际占用
docker exec -it CONTAINER sh

如果变化集中在 /var/log、/tmp、/var/tmp、应用上传目录,重点检查日志轮转、临时文件清理和挂载配置。如果变化来自挂载路径,转而检查卷或宿主机目录,而不是继续依赖 docker diff。

总结

docker diff CONTAINER 可以记成一句话:

查看容器相对于创建时镜像,当前可写层新增、修改和删除了哪些文件。

三个边界必须记住:

  1. 比较的是最终状态,不是完整操作记录。
  2. 主要关注容器可写层,Volume 和 Bind Mount 中的内容需要单独检查。
  3. 只列出路径和变化类型,不提供文本级内容差异。

掌握 docker diff 后,再配合 docker ps --size、docker inspect、docker exec 和普通 diff,就能从"容器变大了"进一步定位到"到底是哪个目录、哪类文件导致的"。

参考资料

相关推荐
lpfasd1233 小时前
Docker存储清理与防膨胀实践
运维·docker·容器
weixin_444579304 小时前
Docker(下):镜像仓库管理
运维·docker·容器
DreamLife☼4 小时前
Agent开发环境搭建完全指南
python·docker·typescript·node·工业知识点
探索云原生4 小时前
Kueue + HAMi vGPU 实战:显存与算力配额管理
docker·ai·云原生·kubernetes·gpu
hoho_125 小时前
docker中升级mysql到最新版本
mysql·docker·容器
cpolar技术支持5 小时前
Kafka Streams 窗口统计怎么验收:本地跑订单流聚合,用 cpolar 给同事看只读结果页
java·docker·kafka·cpolar·kafka streams
广州灵眸科技有限公司6 小时前
瑞芯微(EASY EAI)RV1126B 星闪使用
运维·人工智能·科技·docker·容器
努力努力再努力wz6 小时前
【Docker入门系列】:从 Namespace 到 CGroup:一文理解容器资源控制、cgroup v2 与 Memory Controller 实战
运维·docker·容器
11路没有终点7 小时前
Docker 化测试环境:一致性交付
运维·docker·容器