docker 占满磁盘速查:no space left on device 的分层排查
更新时间:2026-10-24。FAQ 速查页,面向「构建到一半报 no space left on device、
df -h一看根分区 100%」的同学——docker 占满磁盘的本质一句话:docker 的各类产物(镜像层、容器可写层、构建缓存、日志、数据卷)默认都堆在 /var/lib/docker 下,且默认没有任何一个会自己缩——只增不减的东西装在固定大小的盘上,写满是时间问题。病灶收敛在六根轴上。构建期的资源耗尽(内存/CPU)另看 docker 资源耗尽速查;镜像拉不动的限流问题看 docker 拉取限流速查。
本文要回答的问题
docker system df各项数字分别代表什么,哪一项是可回收的?- 悬空镜像(dangling)和停止的容器,清哪个、怎么清?
- 构建缓存(build cache)为什么会吃掉几十 GB?
- 容器日志文件为什么会无声无息涨到几十 GB,怎么限住?
- prune 会误删还在用的东西吗,安全边界在哪?
一、报错原文与典型现象
磁盘写满的指纹分两层——docker 自己的报错和操作系统的报错(路径以 /proj/demo 占位,镜像源用 registry.example.com 占位):
# 指纹一:构建/拉取时写层失败
$ docker build -t registry.example.com/team/app:1.1 /proj/demo
ERROR: failed to solve: failed to copy: write /var/lib/docker/...: no space left on device# 指纹二:容器运行期写文件失败
$ docker exec app touch /data/test
touch: cannot touch '/data/test': No space left on device# 指纹三:df 定位 —— 盘确实满了,且大头在 docker
$ df -h /var/lib/docker
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 60G 60G 0 100% /
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 47 6 28.4GB 21.2GB (74%)
Containers 19 3 4.1GB 3.9GB (95%)
Local Volumes 12 4 6.8GB 5.1GB (75%)
Build Cache 312 0 18.6GB 18.6GB读现象抓三条判据:先跑 docker system df,RECLAIMABLE 列就是可回收量的答案——四项里哪项 RECLAIMABLE 占比高,病灶就在哪;docker system df -v 能展开到每个镜像/容器/卷的明细,定位「具体是谁」;凡是「清了没几天又满」的,根因一定在没有限额机制(日志、缓存),清理只是止血。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| build 报 no space left | 构建缓存堆积 | system df 看 Build Cache | docker builder prune |
| Images 项几十 GB | 悬空镜像与旧 tag 堆积 | docker images -f dangling=true | docker image prune -a |
| Containers 项几个 GB | 停止容器可写层残留 | docker ps -a 看 Exited | docker container prune |
| Volumes 项异常大 | 孤儿数据卷 | docker volume ls 对在用清单 | docker volume prune |
| /var/lib/docker/containers 大 | json-file 日志无限制 | du -sh 各容器日志目录 | 配 max-size/max-file |
| 清完几天又满 | 无定期清理与限额机制 | 看 cron 与 daemon.json | 定期 prune 加日志限额 |
| overlay2 目录超大 | 层泄漏或镜像过多 | du -sh overlay2 对比 df | prune 后核对,异常查挂载 |
| 数据盘满但 df 不满 | 容器内写了未挂载路径 | 进容器 df -h 对比 | 该挂卷的路径挂卷 |
三、排查决策图
这张图是标准排查路线:红是写满现象与根因,蓝是排查动作,绿是验证通过,橙是修复,紫是防复发沉淀。
读这张图抓住关键一步:docker system df 是分流器,先分清「谁占的」再动手清——四类产物的清理命令不同、风险不同,镜像和构建缓存基本随便清,数据卷要确认没在被用。修复的目标不是「清一次」,而是「让它不再涨」:日志限额和定期 prune 才是治本。
四、逐条排查展开
悬空镜像与旧 tag 是最常见的大头。 每次 docker build -t app:1.1 覆盖同名 tag,旧镜像就变成 <none> 的悬空镜像;CI 每天构建几次,一个月能堆出几十 GB。验证:docker images -f dangling=true。处置:docker image prune 只清悬空的;docker image prune -a 连「当前没有容器在用的镜像」一起清——后者更狠,开发机可用,生产机慎用(清掉回滚要用的旧版本就麻烦了)。日常还可以给构建产物镜像打构建号 tag(如 app:1.1.20261024),配合「保留最近 N 个」的清理策略,让悬空镜像从源头减少。
停止的容器不是「不占空间」。 每个容器的可写层在它停止后依然占盘——容器里写过日志、临时文件、下载过依赖的,可写层几个 GB 很常见,几十上百个停止容器累加起来同样可观。docker ps -a 里一排 Exited 就是存量。处置:docker container prune 清掉所有停止容器;日常用 docker run --rm 让一次性任务容器退出即删。
构建缓存默认永不清理。 BuildKit 的构建缓存独立计入 Build Cache 一项,只增不减——docker image prune 清不到它,必须 docker builder prune。长期跑 CI 的机器这一项经常是最大的,十几二十 GB 不稀奇。可以按保留时间清:docker builder prune --filter until=168h(只留最近一周的缓存)。
容器日志是无声的杀手。 json-file 驱动默认不限制大小、不轮转——一个爱打日志的服务跑半年,单个日志文件几十 GB,且 docker system df 不把日志算进任何一项,最容易被忽略。验证:du -sh /var/lib/docker/containers/*/。治本靠 daemon.json 全局限额:
{
"log-driver": "json-file",
"log-opts": { "max-size": "100m", "max-file": "3" }
}改完重启 dockerd 生效,且只对新建容器生效——存量容器要重建才继承限额。验证是否生效:docker inspect <容器> 看 LogConfig 里的 max-size 是否为预期值。
孤儿卷与 overlay2 的边界要分清。 docker volume prune 清的是「没有任何容器引用的匿名卷」,有名字的卷默认不动;overlay2 目录大小 ≈ 所有镜像层加容器可写层之和,prune 之后应该显著回落,如果不回落、且和 system df 对不上,检查是否有已删除文件仍被进程占着(lsof +L1),或容器 bind 挂载写到了宿主目录——那不算 docker 的账,得按普通磁盘排查。
prune 的安全边界要在动手前想清楚。 docker system prune 默认只清停止的容器、悬空镜像、未引用网络,不碰卷;加 --volumes 才连卷一起清——生产机上这条要当成高危操作,匿名卷里可能装着还没来得及备份的数据库数据。经验法则是「镜像和缓存随便清,容器看清状态,卷逐个确认」:清卷前先 docker volume inspect 看挂载点,再用 tar 把内容归档一份,删除永远是最后一步。docker system prune -a 会把没在用的镜像全清掉,包含回滚要用的旧版本——生产环境只允许清悬空镜像,旧版本镜像按「保留最近 N 个」的策略管理。
容器内写满但宿主 df 有空间,是配额与挂载的锅。 一种常见假象:宿主机 df -h 还有空间,容器里写文件却报 no space——原因是写到的是 tmpfs 挂载(默认有大小上限)或容器根文件系统被单独限了额。验证:进容器 df -h 逐挂载点看,定位是哪个挂载写满的;tmpfs 满了调 --tmpfs /run:rw,size=512m 这类参数,或把热数据目录改挂到卷上。
五、规范做法与预防
- 日志限额进 daemon.json 默认模板:max-size 100m、max-file 3,新机器装机即配。
- 定期 prune 进 cron:每周
docker system prune -f加docker builder prune --filter until=168h;生产机 prune 不带-a与--volumes。 - 水位告警前置:磁盘用到 75% 就告警,留清理窗口,不要等 100%。
- 一次性容器 --rm 标配:跑完即删,可写层零残留。
- 数据目录与系统盘分离:/var/lib/docker 挂独立大盘,满了不影响系统其他部分。
- 该挂卷的路径必须挂卷:应用写数据的目录不挂卷,数据就落在容器可写层,既难备份又喂大 overlay2。
这一篇在知识体系里的位置
本篇是 docker 排错速查簇的磁盘资源篇,定位「盘被 docker 自己吃满」:构建期内存/CPU 耗尽看 docker 资源耗尽速查;拉取镜像被限流看 docker 拉取限流速查;构建缓存行为本身(命中与失效)看 docker 构建缓存陈旧速查;容器起不来的权限问题看 docker 权限拒绝速查;容器内时区与日志时间对不上看 docker 容器时区不对速查。限额、定期清理、水位告警,是磁盘不再写满的三件套。
一句话总结
docker 占满磁盘的规律是「四类产物只增不减、日志没有默认限额」:先 docker system df 看哪项 RECLAIMABLE 高,悬空镜像用 image prune、停止容器用 container prune、构建缓存用 builder prune、孤儿卷用 volume prune,json-file 日志要在 daemon.json 配 max-size 与 max-file。排查的通式是先 df 分类再对症清理,确认没在被用的东西才 prune。治本靠日志限额全局模板、定期 prune 进 cron、水位 75% 告警、数据目录独立挂盘,清理只是止血,限额才是疗法。