/var/lib/docker 默认吃掉整个系统盘。镜像叠镜像、退出后躺着的容器、构建缓存一层层堆上去,磁盘告警通常是第一信号。清理这件事的难点在于:Docker 的 prune 家族命令长得像,动手范围差得很远,删错一次,数据库卷就没了。本文把五类对象的删除行为逐条列清,哪些能无脑删、哪些要加过滤、哪些默认根本不碰,看完就能安全回收空间。
清理前先看谁在吃空间
docker system df 是唯一正确的起点,直接动手删等于蒙着眼排雷:
docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 18 4 6.8GB 4.2GB (61%)
Containers 12 3 1.1GB 890MB (80%)
Local Volumes 9 3 12.4GB 2.1GB (16%)
Build Cache 47 0 3.3GB 3.3GB (100%)
RECLAIMABLE 列就是可回收上限。上表这个典型状态里,大头在镜像和构建缓存;卷的 RECLAIMABLE 看着小,但那 16% 可能正是某个已停应用的数据库——回收它之前值得多想一秒。想看细节,加 -v 可以列出每个镜像、容器、卷的实际占用。
镜像:dangling 与未使用,两个概念别混
docker image prune 默认只删 dangling 镜像——没有标签、也没有任何容器引用的悬空层,多数来自重新构建后留下的旧层,删掉零风险:
docker image prune
加 -a 之后范围扩大到所有未被容器使用的镜像。注意判断标准是”当前有没有容器在用”,不是”以后还用不用”——停掉的备份脚本镜像、下周要用的镜像,都在被删之列:
docker image prune -a
想保留最近还可能用到的,用时间过滤。下面这条只删创建超过 24 小时且无容器引用的镜像:
docker image prune -a --filter "until=24h"
| 命令 | 删什么 | 风险 |
|---|---|---|
docker image prune | 仅 dangling 镜像 | 几乎为零 |
docker image prune -a | 所有无容器引用的镜像 | 停用应用镜像会丢,需重新 pull |
加 --filter "until=24h" | 上述范围内创建超 24h 的 | 范围更窄,风险更低 |
容器:退出状态躺久了也是负担
容器停止后不会自动消失(除非启动时带了 --rm),它的可写层继续占磁盘。先看一眼有多少退出容器:
docker ps -a --filter "status=exited"
然后统一清理。要更保守,可以只处理退出超过 24 小时的:
docker container prune
docker container prune --filter "until=24h"
动手前确认一遍:有没有需要 docker logs 回查故障现场的旧容器?排障周期长的服务,把日志先导出来再删。
卷:删错一次,数据就没了
卷是数据库文件、上传附件真正存数据的地方。Docker 的策略很保守——卷永远不会被自动清理,哪怕容器已经删掉。
docker volume prune 默认只删无主匿名卷(没有名字、当前无容器挂载)。但匿名卷正是 bind mount 用惯的人最容易忽视的角落:MySQL 这类镜像不声明卷名时,数据就落在匿名卷里。容器一删,匿名卷就成了孤儿,prune 一到,数据跟着走。
docker volume prune # 只删无主匿名卷
docker volume prune -a # 连命名但无容器挂载的卷一起删,高危
如果确实要跑,先给要保的卷打标签做白名单,再过滤掉它们:
docker volume ls -q | xargs docker volume label 2>/dev/null
docker volume prune --filter "label!=keep"
生产机上的实用动作是逐个核对:
docker volume ls
docker volume inspect <卷名> | grep Mountpoint
ls -lh /var/lib/docker/volumes/<卷名>/_data
能对上文件再决定删不删。这一步多花两分钟,比事后从快照恢复快得多。
网络与构建缓存:副作用各不相同
网络本身不占多少磁盘,但残留的自定义网络会留下 iptables 规则和网桥设备。删掉无容器使用的网络是低风险的:
docker network prune
构建缓存是低配机的隐形大户,反复构建的服务器上积累几个 GB 很常见。注意它会拖慢下一次构建:
docker buildx prune
一步到位全家桶与定时任务
docker system prune 是全家桶:已停止容器、无引用网络、dangling 镜像、构建缓存一次清完,但默认不含卷。加 --volumes 才会把无主匿名卷纳入范围——加不加这个参数,是安全与风险的分界线:
docker system prune -f # 不碰卷,可日常使用
docker system prune -f --volumes # 含无主匿名卷,生产机慎用
-f 跳过确认提示。习惯的做法是挂 systemd timer 每周跑一次不碰卷的版本,再在业务低峰期手动处理卷。
清完之后:让下一次膨胀慢一点
回收空间只解决存量,增量要靠习惯:镜像用 精确版本 tag,少用 latest——拉取更新后旧镜像会立刻变 dangling,这就是悬空层的主要来源;多阶段构建控制镜像层数;数据库一律显式命名卷并配合定期备份(参考 auto-backup-to-cos-oss)。这样每周一条 docker system prune -f 就够,不用碰任何带数据的对象。
常见问题
Q:docker system prune 会把我的数据库数据删掉吗?
默认不会——它不处理任何卷。只有显式加 --volumes 时才会删无容器挂载的匿名卷。生产机上建议固定只用不带 --volumes 的版本。
Q:镜像删多了,下次部署会怎样?
本机镜像被删后,docker compose up 或 docker run 会重新从镜像仓库拉取。代价是时间和流量,国内服务器拉境外仓库可能很慢,重要镜像留 tag 少跑 -a。
Q:until=24h 里的 24h 是什么时间?
相对当前时间的时长,24h 表示创建时间在 24 小时前。也支持 10m、2h30m 等写法。
Q:清理后 docker system df 的数字没变小?
SIZE 统计的层可能被运行中容器共享,删除动作只影响 RECLAIMABLE 的部分。另外构建缓存如果启用了远端 builder,本地 df 看不到它,用 docker buildx du 单独查。
Q:被删的卷还能找回来吗?
不能。卷的删除没有回收站。唯一依靠是删除前的备份:数据库类卷定期 mysqldump/pg_dump 到宿主机或对象存储,匿名卷在清理前先 docker volume inspect 确认 _data 里的内容。
以 docker system df 开头、以分层清理收尾,磁盘告警就能从救火变成例行维护。记住一条就够:清理顺序从镜像和缓存开始,卷永远最后碰——前者最坏重拉一次,后者最坏丢的是数据。