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 prune | build 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/* 的结果对不上时,说明有对象没被判定为可回收,或者已经被删除但目录项还在。先用两个口径对照,再决定下一步删什么,不要直接对目录动手。
执行顺序
docker system df -v看清各类型的 SIZE 与 RECLAIMABLE,确认空间到底压在哪一类。- 小步执行:先
docker container prune,再docker image prune(不带-a),需要时单独跑docker builder prune。 - 复查
docker system df -v与df -h,确认差额来自哪一类,再决定是否上-a或--volumes。