资讯动态

Docker磁盘清理全攻略:从一键命令到分级策略与安全脚本

发布时间:2026/10/27 19:45:09 来源:尧图企业网站定制
1. 为什么“一键清理”这件事值得认真对待跑容器的人都有一个共同的默契磁盘不是慢慢满的是某天突然就满了。早上到工位docker ps一切正常业务日志也没报错结果一编译镜像就提示no space left on device再一看根分区/var/lib/docker已经吃掉了几十上百个 G。这时候大多数人的第一反应是上网搜一条“一键清理 Docker 磁盘”的命令复制粘贴回车看到释放了几个 G长舒一口气然后过两周同样的事情再来一遍。我见过太多团队在这个循环里打转。问题不在于那条命令本身对不对而在于“一键”这两个字容易让人放弃判断。Docker 的磁盘占用是有明确结构的镜像层、容器可写层、数据卷、构建缓存、日志文件每一块的增长逻辑都不一样清理的代价也完全不一样。有些东西删了毫无影响有些东西删了等于把数据库卷一起扬了。所以这篇内容我想做的事情很直接把“一键清理 Docker 磁盘文件”这个动作彻底拆开讲清楚每一类占用是怎么来的、哪些能放心删、哪些碰都不能碰、一条安全的清理脚本应该长什么样以及怎么让磁盘不再反复被撑爆。适合谁来读如果你是被磁盘告警追着跑的运维或后端开发这篇能直接拿去用如果你刚开始接触容器对docker system prune到底删了什么心里没底这篇会把每一层都摊开给你看如果你所在的团队还没有形成清理规范文末那套“先诊断、再分级、后自动化”的思路可以直接搬过去。全文围绕实操展开命令都是可以直接复制的但每一条我都会告诉你它动了什么、为什么这么动。2. 先搞清楚 Docker 到底把磁盘吃在哪了2.1 五类占用的结构拆解在动手删任何东西之前得先有一张“磁盘地图”。Docker 在宿主机上的存储默认落在/var/lib/docker可以通过docker info里的Docker Root Dir确认实际路径这个目录下面大致是这么几块占用类型典型路径增长来源删除风险镜像层overlay2/每次构建、每次拉取新版本低但会导致下次重新拉取容器可写层overlay2/中容器 diff 目录容器内写文件、日志未外挂中容器删除即丢失数据卷volumes/数据库、上传文件等持久化数据极高删了数据就没了构建缓存buildkit/频繁构建镜像产生的中间层低仅影响下次构建速度容器日志容器 json-log 文件应用 stdout 输出未做轮转低但需先解决根因这张表是后面所有操作的判断依据。很多人执行docker system prune -a之后发现磁盘没降多少就是因为真正的大头在数据卷或者日志上而这条命令默认不碰卷。反过来有人为了省事加了--volumes结果把某个没做备份的数据库卷清空了这种事故在社区里并不罕见。2.2 用三条命令建立全局视图诊断阶段我习惯只用三条命令信息量足够且不会误伤# 第一条看整体账本镜像/容器/卷/缓存各占多少 docker system df -v # 第二条看宿主机层面 /var/lib/docker 的真实占用 sudo du -sh /var/lib/docker/* 2/dev/null | sort -rh | head -20 # 第三条找出体积最大的前若干个容器日志文件 sudo find /var/lib/docker/containers -name *-json.log -size 100M -exec ls -lh {} \;docker system df -v是最重要的一条它会列出每个镜像、每个容器、每个卷的具体大小RECLAIMABLE那一列直接告诉你“删了能回收多少”。我一般会先看这一列的总和如果可回收空间远小于磁盘缺口说明问题不在镜像和缓存上得往日志和卷的方向查。第二条命令是交叉验证因为docker system df统计的是 Docker 自己认知的范围有时候宿主机上还有一些游离的文件比如被删除但被进程占用的文件du能看到真实情况。第三条专门针对日志容器日志撑爆磁盘是最隐蔽的一种因为docker system df里它被算在容器可写层里不单独显示。提示如果du显示的空间和df显示的已用空间对不上大概率是有进程持有已删除文件的句柄。用sudo lsof L1可以找出来重启对应进程即可释放不需要删任何 Docker 对象。2.3 一个容易被忽略的坑磁盘配额与 inode有些环境磁盘没满但就是写不进去这时候要看 inode。df -i一眼就能看出来。Docker 的镜像层是小文件密集型存储一个中等规模的环境轻松就能堆出几十万 inode。如果 inode 耗尽表现和磁盘满几乎一样但清理思路不同——这时候删大文件没用得删文件数量多的对象比如大量小镜像或者构建缓存。我在一个持续集成环境里遇到过这个问题磁盘还有 40% 空闲但 inode 用了 98%最后是靠清构建缓存解决的。所以诊断阶段顺手看一眼df -i能省掉后面很多弯路。3. 分级清理策略从零风险到需确认3.1 第一级零风险清理随时可做这一级的操作不会删除任何还在被使用的对象属于“闭着眼睛执行也没事”的范畴。# 清理已停止的容器 docker container prune -f # 清理悬空镜像没有 tag 的中间层 docker image prune -f # 清理未被任何容器使用的网络 docker network prune -f # 清理构建缓存保留最近使用的部分 docker builder prune -f --filter until168hdocker image prune -f不带-a的时候只删悬空镜像也就是那些none:none的中间层。这些层通常是构建过程中产生的删掉不影响任何带 tag 的镜像。构建缓存那条我加了--filter until168h意思是只清理一周前的缓存最近用过的保留下来这样下次构建还能命中缓存不至于为了省空间把构建速度也搭进去。这个参数是我踩过坑之后加的——早期我直接docker builder prune -f全清结果团队里每个人下次构建都慢了好几分钟被抱怨了很久。这一级操作的总回收量通常不大但在日常维护中坚持做能避免小问题滚成大问题。我一般建议把它做成定时任务每天凌晨跑一次成本几乎为零。3.2 第二级低风险清理需确认无引用这一级会删除带 tag 的镜像和构建缓存的全量需要先确认没有正在运行的容器依赖它们。# 删除所有未被运行中容器使用的镜像 docker image prune -a -f # 全量清理构建缓存 docker builder prune -a -fdocker image prune -a的语义是“删除所有没有被至少一个运行中容器引用的镜像”。注意关键词是“运行中”已停止的容器不算。所以执行前最好先docker ps -a看一眼有没有重要的停止容器如果有要么先启动它要么接受它的镜像被删掉容器本身还在但重新启动会失败。这个细节很多人不知道以为-a只是删“没用的”实际上停止的容器所依赖的镜像也会被判定为可回收。构建缓存全量清理的代价是下次构建从零开始。如果你的项目构建一次要十几分钟这个操作要慎重最好放在发布窗口之后做。我通常的做法是保留最近 24 小时的缓存用--filter until24h兼顾空间和效率。3.3 第三级高风险操作必须逐项确认到了这一级每一条命令执行前都应该先“预演”一遍看清楚要删什么。# 先列出所有卷人工确认哪些可以删 docker volume ls # 预演列出会被删除的卷不实际删除 docker volume prune --filter label!keep --dry-run 2/dev/null || docker volume ls -f danglingtrue # 确认后执行 docker volume prune -fdocker volume prune默认只删除“悬空卷”也就是没有被任何容器包括停止的容器引用的卷。听起来安全但实际场景里经常有卷是临时没挂载、过几天要用的一删就麻烦了。我的习惯是给所有需要保留的卷打上label比如docker volume create --label keeptrue db_data然后清理时用--filter label!keep排除掉。这样即使手滑执行了 prune重要数据也不会丢。这个习惯是从一次事故里学来的——某次清理后第二天要恢复一个测试环境发现卷没了只能从头造数据。注意docker system prune --volumes这条命令把容器、镜像、网络、卷一锅端是风险最高的一条。除非你非常确定环境里没有任何持久化数据否则不要用。我个人的原则是卷永远单独处理绝不和镜像清理混在一条命令里。3.4 容器日志最隐蔽的空间杀手日志清理严格来说不属于 Docker 对象清理但它往往是磁盘告警的真正元凶。默认情况下容器的 stdout/stderr 会写到宿主机的 json 文件里没有任何大小限制一个话痨应用一天写几十 G 完全可能。# 查看所有容器日志大小并排序 sudo du -sh /var/lib/docker/containers/*/*-json.log 2/dev/null | sort -rh | head -10 # 清空单个日志文件不删除文件本身避免影响运行中的容器 sudo truncate -s 0 /var/lib/docker/containers/容器ID/容器ID-json.log这里必须用truncate -s 0而不是rm。直接删文件的话容器进程还持有文件句柄磁盘空间不会释放而且 Docker 会继续往一个已经不存在的文件里写行为不可预期。truncate把文件截断为 0 字节句柄不变空间立即释放是最稳妥的做法。但清空只是止血根治要靠日志轮转。在/etc/docker/daemon.json里加上{ log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }这段配置的意思是每个容器日志单文件最大 100MB最多保留 3 个也就是单容器日志上限 300MB。改完需要sudo systemctl restart docker生效且只对新建容器起作用已有容器需要重建。这个参数值可以根据应用日志量调整我一般给业务容器设 100m×3给日志量大的网关类容器设 200m×5总量控制在 1G 以内。4. 把清理做成可复用的脚本4.1 脚本设计思路手动敲命令适合偶尔维护但磁盘告警往往是突发的半夜被叫起来敲命令体验很差。所以我把上面三级策略固化成一个脚本核心设计原则有三条默认只做零风险和低风险操作、高风险操作必须显式传参、每一步都打印回收量便于复盘。#!/usr/bin/env bash set -euo pipefail MODE${1:-safe} # safe | standard | aggressive echo 清理前磁盘状态 df -h /var/lib/docker | tail -1 docker system df case $MODE in safe) echo 执行零风险清理 docker container prune -f docker image prune -f docker builder prune -f --filter until168h ;; standard) echo 执行标准清理含未使用镜像 docker container prune -f docker image prune -a -f docker builder prune -f --filter until24h ;; aggressive) echo 执行激进清理含悬空卷请确认 read -rp 确认删除悬空数据卷输入 yes 继续: ans [[ $ans yes ]] || { echo 已取消; exit 1; } docker container prune -f docker image prune -a -f docker builder prune -a -f docker volume prune -f ;; *) echo 未知模式: $MODE; exit 1 ;; esac echo 清理后磁盘状态 df -h /var/lib/docker | tail -1 docker system df这个脚本的关键点在于aggressive模式加了一道read确认防止被误调用。set -euo pipefail保证任何一步失败就停下不会出现“删了一半”的中间状态。前后各打印一次磁盘状态方便对比回收效果也方便事后追溯“到底哪次清理释放了多少”。4.2 参数选择的计算依据脚本里几个时间参数不是拍脑袋定的。safe模式保留 168 小时7 天的构建缓存是因为大多数团队的构建频率在一周内会覆盖所有活跃分支7 天前的缓存命中率已经很低删掉性价比最高。standard模式保留 24 小时是因为这个模式通常在有空间压力时手动触发保留一天的缓存能保证当天的持续集成不受影响。这两个数字可以根据团队的实际构建节奏调整判断标准是统计一下最近 N 天构建缓存的命中率命中率低于 20% 的那部分就可以放心删。镜像清理的-a参数在standard模式里启用前提是团队有镜像仓库作为后盾删了还能重新拉。如果镜像是本地构建且没有推送仓库那-a就要慎用因为删了就真没了得重新构建。这一点在脚本注释里应该写清楚避免接手的人误用。4.3 定时任务与告警联动脚本写好之后挂到 cron 里每天跑一次safe模式# 每天凌晨 3 点执行安全清理日志留存 0 3 * * * /opt/scripts/docker-clean.sh safe /var/log/docker-clean.log 21同时在监控系统里加一条规则/var/lib/docker使用率超过 80% 告警超过 90% 触发standard模式清理。这样形成“日常小清、告警大清”的节奏基本不会再有磁盘满导致构建失败的情况。我所在的团队用这套机制跑了大半年磁盘告警从每周几次降到几乎为零而且每次清理都有日志可查出问题能快速定位是哪次清理动了什么。5. 实操中踩过的坑与排查速查表5.1 几个真实踩坑记录第一个坑是docker system prune -a之后镜像没了但空间没释放多少。查了半天发现是构建缓存没清因为那条命令默认不清 BuildKit 的缓存得单独docker builder prune。这个细节在文档里写得很清楚但实际操作时很容易漏。第二个坑是清空日志后空间没变。原因是有进程还持有已删除文件的句柄truncate对这种情况无效得先重启容器或者用lsof L1找到持有者。后来我养成了习惯清日志前先确认容器是否在运行运行中的用truncate已停止的直接删文件。第三个坑是卷清理误伤。早期用docker volume prune没加过滤把一个暂时没挂载的备份卷删了。从那以后所有重要卷都打keep标签清理时用--filter label!keep排除。这个习惯成本很低但能避免灾难性后果。第四个坑和 inode 有关。一个持续集成环境磁盘还有空间但构建一直失败最后发现是 inode 耗尽。清理思路和大文件完全不同得删文件数量多的对象比如大量小镜像层和构建缓存。现在我的诊断清单里固定有df -i这一项。5.2 常见问题速查表现象可能原因排查命令处理方式清理后空间没释放进程持有已删除文件句柄sudo lsof L1重启对应进程磁盘有空间但写入失败inode 耗尽df -i清理小文件密集型对象镜像删了空间没降构建缓存未清docker system df -vdocker builder prune容器启动失败提示镜像不存在镜像被 prune 删除docker images重新拉取或构建日志文件巨大未配置日志轮转du -sh .../*-json.log配置 daemon.json 并重建容器卷数据丢失prune 误删悬空卷docker volume ls从备份恢复后续加 label这张表我打印出来贴在工位上遇到问题先对号入座大部分情况三分钟内能定位。表里的排查命令都是只读的可以放心执行不会造成二次破坏。5.3 预防优于清理的三条经验清理做得再顺也不如让磁盘不涨。第一条经验是给所有容器配日志轮转这一条能消灭大部分磁盘告警。第二条经验是构建缓存设上限在daemon.json里配置builder: {gc: {enabled: true, defaultKeepStorage: 20GB}}让 BuildKit 自动回收不用手动管。第三条经验是数据卷和临时数据分离数据库、上传文件这类必须持久化的走命名卷并打标签临时数据走容器可写层或者tmpfs这样清理时边界清晰不会误伤。这三条落地之后清理脚本从“救火工具”变成了“兜底保险”磁盘管理这件事才算真正闭环。我个人在实际操作中的体会是一键清理的价值不在于那一条命令而在于背后对每一类占用的清晰认知和分级策略。命令谁都会复制但知道为什么这么删、删了会怎样才是把这件事做稳的关键。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑