编程 docker prune 删完磁盘没降:未使用判定与三类残留探针

2026-11-10 00:03:34

docker prune 删完磁盘没降:未使用判定与三类残留探针

docker image prune、docker system prune 的输出里写着 reclaimed 了多少空间,回头 df -h 却没变化,这个落差通常不是命令没生效,而是「未使用」的判定口径和磁盘实际占用不是一回事。这篇只看三个判定点:镜像和容器之间的引用关系、build cache 作为独立记录怎么算、volume 的引用链什么时候断。备份仓库那类清理(restic forget/prune 按快照策略删并回收 pack 文件)是另一套模型,这里不展开。

各条命令分别删什么

命令删除对象删带 tag 的未引用镜像动 volume
docker image prune仅 dangling 镜像(无 tag 且无容器引用)否否
docker image prune -a所有无任何容器引用的镜像是否
docker container prune所有已停止容器不涉及否
docker volume prune未被任何容器引用的 volume不涉及是
docker builder prunebuild cache(默认 dangling 部分)不涉及否
docker system prune已停止容器 + 未使用 network + dangling 镜像 + dangling build cache默认否,加 -a 才扩大默认不动,需 --volumes

「未使用」的判定

dangling 与 -a 的边界

dangling 的定义是两条同时成立:镜像没有 tag(显示为 :),且不被任何容器引用。docker image prune 不带 -a 时只处理这一批。

-a / --all 把条件放宽到「没有任何容器引用」,带 tag 的本地旧版本也会进来,例如上一个 release 留下的 app:v1.2。CLI 的警告原文是 "all images without at least one container associated to them",这句话本身就是判定式。

停止的容器仍然算引用

引用关系看的是容器对象是否存在,不看它是否在运行。一个 Exited 状态的容器只要还在 docker ps -a 里,它用的镜像就不算「无引用」。所以顺序上必须先 docker container prune 把停止容器清掉,镜像才有机会在后续 prune 里被回收。反过来先跑 image prune -a,看到的可回收量会比预期小。

匿名 volume 的引用链

docker volume prune 删的是「未被任何容器引用」的 volume。匿名 volume 由容器创建,只要创建它的容器还在——哪怕已经停止——这个 volume 就仍被引用。容器删除之后,引用才断,volume 才进入可删范围。

docker system prune 默认不带 --volumes,加不加由执行者决定;这一点在脚本里容易被忽略。

走 API 时,filters 是唯一入口

client.ImagesPrune 没有对应 CLI 的 --all、--force 参数,范围完全由 filters 决定:

  • dangling=true(或 1):只清无 tag 的未使用镜像,对应 image prune。
  • dangling=false(或 0):清所有未使用镜像,对应 image prune -a。

按 API 文档,不传 filter 与 dangling=true 的处理等价。常见错误正好相反:以为传 true 表示「全部」,结果只清了悬空镜像。

f := filters.NewArgs()
f.Add("dangling", "false") // 想要 image prune -a 的效果

report, err := cli.ImagesPrune(ctx, f)
if err != nil {
return err
}
log.Printf("space reclaimed: %d bytes", report.SpaceReclaimed)

report.SpaceReclaimed 是这次调用声明的回收字节数,可以作为日志里的核对项。

直接用 HTTP 时,filters 要 JSON 编码再 URL encode,请求走 unix socket:

curl --unix-socket /var/run/docker.sock -X POST \
"http://localhost/v1.46/images/prune?filters=%7B%22dangling%22%3A%7B%22false%22%3Atrue%7D%7D"

until 只对部分资源生效

--filter until=24h 不是通用参数,各资源支持情况不一样:

  • 镜像:until 按创建时间算,另外支持 dangling、label。
  • build cache:until 按最后使用时间算。
  • 容器:until 按创建时间算,支持 label。
  • network:只支持 until。
  • volume:支持 label,不支持 until。

写清理脚本时,把 until 套到 volume 或 network 上不会按预期过滤,需要先确认该资源是否支持。

build cache 是独立的一条记录

build cache 不挂在镜像下面,builder prune 单独处理。docker system prune 默认只清 dangling 的 build cache,完整的缓存要走单独的 builder prune。

buildx 的 --all 语义和 image prune -a 不同:它清理的是全部 build cache,而不是按「无引用镜像」的口径筛选。这个差异在官方 reference 页有说明,脚本里两个 --all 不能互相套用。

探针两条:

docker system df -v     # 每类资源的 SIZE 与 RECLAIMABLE
docker buildx du        # build cache 的明细占用

磁盘没释放的三种残留

1. 已删除但仍被进程打开的文件。 层文件被 unlink 之后,只要容器内进程还持有 fd,空间就不回内核。lsof +L1 能看到这类 deleted 条目,但只有在运行中的容器/进程命名空间里才可见,宿主机上不一定列得出来。这类空间要等进程退出或容器重启才回收,prune 命令本身管不到。

2. 日志文件不受 prune 影响。 默认 json-file 驱动写的 container json.log 不在任何 prune 的删除范围内,长期运行的服务会把它堆到几十 GB。需要在 daemon 配置里设 log-opts 的 max-size 与 max-file,否则清完镜像,占用很快又回来。

3. /var/lib/docker 下的 overlay2 与目录残留。 docker system df -v 给出的 RECLAIMABLE 是估算值,和 du -sh /var/lib/docker/* 的结果对不上时,说明有对象没被判定为可回收,或者已经被删除但目录项还在。先用两个口径对照,再决定下一步删什么,不要直接对目录动手。

执行顺序

  1. docker system df -v 看清各类型的 SIZE 与 RECLAIMABLE,确认空间到底压在哪一类。
  2. 小步执行:先 docker container prune,再 docker image prune(不带 -a),需要时单独跑 docker builder prune。
  3. 复查 docker system df -v 与 df -h,确认差额来自哪一类,再决定是否上 -a 或 --volumes。

参考

复制全文 生成海报 prune Docker 运维实战 磁盘空间

推荐文章

程序员茄子在线接单