磁盘又快满了。如果你看到系统监控里那个磁盘使用率的红色曲线一路爬升,或者执行命令时突然报出no space left on device,大概率就是 Docker 在悄悄吞噬你的硬盘。这个坑我踩过不止一次,而且每次排查完都会发现,问题几乎都集中在 overlay2 目录、容器日志和 Build Cache 这三块上面。
这篇文章就把我的排查思路和清理动作完整记录下来。不管你是刚入门 Docker 的新手,还是被生产环境磁盘告警折磨过的运维,这套方法都适用。我会从原理讲到实操,把每一步命令的作用、预期输出和坑点全说清楚。
1. 内容整体设计与思路拆解——先搞懂 Docker 磁盘空间去向
1.1 Docker 为什么会吃掉大量磁盘空间
要清理 Docker 的磁盘占用,第一件事不是急着执行删除命令,而是先理解 Docker 的数据都存放在哪里。默认情况下,Docker 的所有数据都集中在/var/lib/docker目录下(通过docker info可以确认当前 Docker Root Dir 的位置)。这个目录下最占空间的通常就是几个关键子目录:overlay2、containers、volumes和buildkit。
拿生活的例子来类比:overlay2就像你的衣柜里堆满的旧衣服,containers里的日志就像每天都在变厚的报纸堆,buildkit就像装修时留下的各种边角料。这三者堆在一起,再大的硬盘也扛不住。理解了这个结构,你就知道清理的重点方向了。
1.2 镜像层与容器可写层的核心机制
Docker 镜像采用分层设计,每一层都是只读的,多个镜像还可以共享相同的底层文件。容器的读写发生在最顶层的“容器可写层”。这种设计的本意是节省空间——基础镜像只存一份,所有基于它的容器共享即可。
但问题恰恰出在这里:当你反复执行docker build或docker run,会产生大量的临时层;当你删除容器时,如果没有加-v参数,对应的可写层虽然会删除,但里面产生的数据也一并没了;如果加了-v又把数据卷挂载到外部,那删除容器后数据卷依然会保留。这些机制的细节直接决定了清理时你需要命令的选择,所以先花点时间把原理搞清楚,后面操作不会手忙脚乱。
1.3 三个核心排查方向的优先级排列
我的习惯是按照“成本从低到高、风险从低到高”的顺序来排查:第一步看镜像和容器占用量(docker system df),第二步看 overlay2 里可写的垃圾数据,第三步看容器日志的大小,第四步才是构建缓存(因为构建缓存清理后下次构建要重新拉取和编译,有一定的时间成本)。
这种排序的另一个好处是:前两项排查完全无风险,第三步可以用truncate -s 0原地清空而不是删除文件,避免容器持续写日志时出现文件句柄错误。后面我会具体展开每一步的命令和判断依据。
2. 核心细节解析与实操要点——先从“能看到的地方”查起
2.1 第一板斧:docker system df快速定位大盘
排查磁盘占用,我强烈建议第一步执行:
docker system df这个命令会告诉你四类资源的使用情况:镜像、容器、本地卷(Local Volumes)和构建缓存(Build Cache)。每一行都有 SIZE 和 RECLAIMABLE 两列,RECLAIMABLE 表示可回收空间的大小,它是由未使用的镜像层、已停止的容器、悬空镜像和未使用的数据卷共同决定的。
便于对比,这里给出一个典型输出:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 28 5 5.891GB 4.124GB (70%) Containers 32 3 1.028GB 1.017GB (99%) Local Volumes 6 3 780.2MB 512.3MB (66%) Build Cache 156 0 12.58GB 12.58GB (100%)如果看到类似数据,说明镜像、容器和 Build Cache 都偏高,尤其是 Build Cache 12.58GB 全部可回收,这类空间在长期使用 CI/CD 或频繁构建的机器上非常可观。docker system df还有一个-v参数,能看到更细粒度的卷和容器列表,适合精确确认。
2.2 第二板斧:du -sh从磁盘层面复核
docker system df显示的是 Docker 内部统计的镜像层和容器的虚拟大小,它统计的是“数据量”而不是实际硬盘占用。要确认真实的硬盘占用,还需要用du检查 Docker 根目录:
sudo du -sh /var/lib/docker/*在我处理过的一台故障机器上,这个命令的输出是:
12G /var/lib/docker/containers 45G /var/lib/docker/overlay2 3.2G /var/lib/docker/volumes 13G /var/lib/docker/buildkit看到了吗?真正“吃”掉磁盘的大头是overlay2和containers。其中containers下存放的其实是每个容器的配置文件、日志和容器的可写层元数据,日志文件就藏在/var/lib/docker/containers/<容器ID>/*-json.log里面。也就是说,日志的体积会直接反映到containers目录的大小上。
2.3 第三板斧:找出“哪个容器在疯狂写日志”
如果你看到containers目录异常大,就需要精确定位是哪个容器在疯狂输出日志。可以用下面这条命令一键列出所有容器日志文件的大小:
sudo find /var/lib/docker/containers -name "*-json.log" -exec ls -lh {} \;输出里能看到每个容器日志文件的完整路径和大小,看到十几 GB 的-json.log文件也不要惊讶,生产环境里我见过单个容器日志写到 50GB 以上的情况。如果要更直观地映射到容器名称,可以加一步:
for c in $(docker ps -aq); do log_file=$(docker inspect --format='{{.LogPath}}' "$c") size=$(du -sh "$log_file" 2>/dev/null | awk '{print $1}') echo "$c $(docker inspect --format='{{.Name}}' "$c") $size" done这条命令用docker inspect获取每个容器的日志路径,再逐个体积统计。对输出的排序一眼就能看出日志最大的容器是哪几个,便于后续决定是清理日志还是调整日志轮转策略。
3. 实操过程与核心环节实现——重头戏清理动作
3.1 清理容器日志:两种场景,两种选择
容器日志清理是最容易见效的,但我强调一句:不要直接用rm删除日志文件。当容器还在运行时,直接删除文件并不会释放空间——因为文件句柄还被容器进程握着,磁盘空间只有等容器重启或日志轮转触发后才会真正释放。更关键的是,删除后容器继续写入日志会因文件句柄仍然有效而产生诡异的分裂写入行为。
第一种场景:容器没在运行,或者可以接受短暂重启。这时直接删除日志文件最干净,删除完如果容器还在 Docker 的容器列表里,重启它就会自动创建新的空日志文件。
第二种场景:容器正在跑,不能随便重启。正确做法是原地清空文件:
sudo truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.logtruncate -s 0会将文件大小立即归零,但文件句柄不变,正在运行的容器不会感知到任何异常,可以继续写日志。实测下来这是最平滑的清理方式,生产环境基本可以做到无感知回收空间。
3.2 根治日志膨胀:配置 Docker 全局日志轮转
清理只能解决眼前问题,如果不改配置,日志还会继续膨胀。真正有效的方案是给 Docker daemon 配置日志轮转。编辑/etc/docker/daemon.json:
{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }含义是每个容器日志达到 50MB 后自动滚动轮转,最多保留 3 个文件。这样单个容器的日志总占用被限制在 150MB 以内,机器上的容器数量再多也不会出现日志把磁盘打爆的情况。
配置完成后的生效步骤是:
sudo systemctl daemon-reload sudo systemctl restart docker注意:修改 daemon.json 并重启 Docker 只对之后新建的容器生效,已经存在的容器不会自动应用新策略。对存量容器,有两种处理方式:一是重建容器(自然是重新docker run);二是先手动清理当前日志,然后等后续容器重建时自动应用。
3.3 清理 overlay2 目录:不能直接删,要借助 Docker 的机制
overlay2之所以体积大,主要来源有两块:镜像层的缓存副本和已删除容器遗留的可写层数据(又叫“孤儿层”)。这些孤儿层产生的原因很常见:你创建容器、写入了不少数据,然后docker rm删除容器,但没有通过docker system prune等命令回收关联镜像层时,一部分数据残留在了 overlay2 目录。
那么能不能手动进入/var/lib/docker/overlay2直接删除目录?千万不要。Docker 的文件系统设计依赖目录结构和硬链接关系来跟踪镜像层,直接动底层目录轻则带来镜像损坏,重则所有容器无法启动。清理 overlay2 的正确方式是使用 Docker 官方命令,它们会通过内部引用计数去识别可删除的层,安全并且精确。
执行以下三次命令,按提示输入y确认:
docker container prune docker image prune docker volume prune每次prune都会显示回收的空间大小。如果希望一次性清理所有未使用资源,可以用:
docker system prune -a --volumes这里解释一下参数的含义:-a表示删除所有未被容器使用的镜像,而不只是悬空镜像;--volumes会额外删除没有容器引用的数据卷。这个动作比较大,比如某个数据卷是后期要用来恢复数据的,一旦删了就再也找不回来了。所以用-a --volumes之前,务必先确认卷列表:
docker volume ls3.4 单独的风向标:Build Cache 清理
现在单独说说 Build Cache。只要是频繁执行docker build的机器,Build Cache 的大小都相当可观。Docker 的构建缓存机制会缓存每一层构建的结果,相同指令在后续构建中直接复用,从而加快构建速度。但缓存不会无限期自动淘汰,时间一长就堆积成“存储黑洞”。
查看缓存占用:
docker system df清理 Build Cache:
docker builder prune如果想清理得干净一些:
docker builder prune -a -f-a表示删除所有构建缓存(包括仍在使用的),-f表示不需要交互确认。执行后会有进度提示和释放空间统计。需要知道的一个行业经验是:在 CI/CD 场景下,构建缓存的体积往往比实际运行的镜像还要大。因为每次代码提交都可能触发一次构建,新的构建层会不断产生,而旧的悬空层虽然不用于任何容器运行,却仍占用着磁盘空间。
3.5 Docker Desktop 与 WSL2 用户的磁盘问题
如果你是在 Windows 上用 Docker Desktop,遇到的问题稍有不同,但思路一样。Docker Desktop 在 Windows 上往往基于 WSL2 运行,它导出的docker-desktop-data虚拟磁盘文件(ext4.vhdx)会一直增长,即使清理了容器和镜像,这个虚拟磁盘文件的大小也不会自动收缩。也就是说,你在容器里删除了几十 GB 数据,宿主机 C 盘可能还是一样满。
处理方式是在 WSL 中执行磁盘压缩:
wsl --shutdown然后以管理员身份打开 PowerShell:
Optimize-VHD -Path "C:\Users\你的用户名\AppData\Local\Docker\wsl\data\ext4.vhdx" -Mode FullOptimize-VHD是 Hyper-V 自带的虚拟磁盘优化命令,只有在此前已启用 Hyper-V 的情况下可用。如果没启用 Hyper-V,可以换一个思路:在资源管理器中找到ext4.vhdx文件,通过“磁盘清理”工具来删除临时文件后,用 DiskPart 中的compact vdisk命令手动压缩。这类场景比较繁琐,但能有效回收几十 GB 的 C 盘空间。
4. 常见问题与排查技巧实录——那些容易踩的坑
4.1docker system df显示很小但磁盘满了
这种情况最容易让人困惑。明明 Docker 的各项统计数据都不大,但系统磁盘还是爆满。原因通常有两个:一是 Docker Root Dir 可能在别的分区,比如daemon.json 里配置了>sudo du -xh --max-depth=1 / | sort -hr | head -20
这条命令找出根目录下最大的前 20 个目录。逐层执行du,很快就能定位到大文件到底藏在哪个目录。实际排查经验中,遇到最离谱的一种情况是某个容器把数据直接写进了容器可写层(路径在/var/lib/docker/overlay2/<layer-id>/diff/...),这时镜像列表显示占用不大,但可写层膨胀到了几十 GB。遇到这种就只能通过重建容器并修改容器内数据写入逻辑来解决。
4.2 清理 Build Cache 后构建变慢
执行docker builder prune -a后,下次构建确实会明显变慢,因为所有层都要重新拉取基础镜像、重新执行构建指令。这是正常的,不应当因此就不清理缓存。更合理的做法是分级处理:频繁构建的开发机保留一定规模的缓存,把清理动作安排在低峰期;CI 机器则可以在每次构建前主动清理缓存,避免缓存无限增长,反正 CI 构建本来就是从头开始的。
我个人的习惯是用一个阈值来判断:当docker system df显示 Build Cache 超过 10GB 时,执行一次docker builder prune -f(保留正在使用的缓存),再考虑是否需要-a。如果磁盘确实紧张,那就毫不犹豫地用-a,毕竟空间比缓存更重要。
4.3 删了镜像但磁盘没释放
镜像删除后磁盘没释放,多见于两种场景。第一种:镜像被某个已停止的容器引用着,删除镜像时 Docker 会拒绝或只删除镜像链中没被引用的层,剩余层依然占据空间。解决方法是先清理停止的容器再清理镜像。第二种:日志文件或数据卷还残留在大目录中,你删除了镜像,但没有清理掉它创建的数据卷。
一个规范顺序是:
docker container prune docker image prune -a docker volume prune docker builder prune这个顺序符合引用关系:先清容器,再清镜像,接着清卷,最后清构建缓存。按这个顺序执行,绝大多数情况下可以一次性把可回收空间全都找回来。
4.4 生产环境实战排查记录
给你看我最近处理的一台生产机器上的完整排查记录。最开始系统的告警是磁盘使用率超过 95%,登录后执行df -h,确认根分区使用了 96%。接着按顺序排查:
docker system df输出显示镜像占 8GB(回收 6GB),容器占 3GB(回收 3GB),构建缓存占 20GB(全部可回收)。
但执行完docker system prune -a --volumes之后,磁盘使用率只降到 89%,和预期的“释放 29GB”差了很远。于是我继续用du检查:
sudo du -sh /var/lib/docker/containers/*/*-json.log这一查就发现了真凶:一个日志文件已经暴涨到 33GB,是当时运行的某个中间件容器在疯狂输出调试日志。我执行truncate -s 0清空该日志,磁盘使用率立刻降到 71%。后续我修改了容器启动参数,给 Docker daemon 配置了日志轮转限制。
回过头来看,整个排查过程的核心就是从系统层面入手,先从磁盘的“真正拥有者”摸起,再结合 Docker 的统计命令去比对,两条线都走通之后才能制定精准的清理策略。
5. 长效维护机制——如何避免磁盘再次被撑爆
5.1 配置 daemon.json 的完整建议
前面提到日志轮转配置,这里给出一个更完整的daemon.json供参考:
{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" }, "storage-driver": "overlay2", "storage-opt": [ "overlay2.size=50G" ] }最后一行overlay2.size=50G为容器可写层设置了一个总容量上限(限流),当可写层数据超过 50GB 时,Docker 会拒绝继续写入。这在多人共用一台 Docker 主机时非常实用,能避免某个用户的容器把磁盘打满导致所有服务崩溃。
5.2 定时任务自动清理
如果没有停机窗口,可以配置一个定时清理脚本,比如每天凌晨 2 点执行只清理悬空资源的任务:
0 2 * * * /usr/bin/docker system prune -f --filter 'until=48h' >/dev/null 2>&1--filter 'until=48h'只清理超过 48 小时未被使用的资源,风险更低。需要注意docker system prune默认不会清理数据卷,所以这个脚本不会误删卷数据,适合作为日常维护任务。每周可以手动执行一次更大的清理,按需搭配--volumes。
5.3 监控预警
配置磁盘使用率的监控告警非常有必要。可以在节点上安装node_exporter(Prometheus 生态)搜集磁盘指标,设置一个 80% 的告警阈值。至少也要在 cron 里挂一个简单的磁盘使用率检测脚本:
#!/bin/bash THRESHOLD=80 USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$USAGE" -gt "$THRESHOLD" ]; then echo "Disk usage is above $THRESHOLD%: ${USAGE}%" | mail -s "Disk Alert" your@email.com fi磁盘告警的价值在于:所有清理命令都是有损的,最稳定的状态是让磁盘占用停留在可控范围内,而不是每个月都做一次“大扫除”。通过监控预警把问题消灭在早期,是更优雅的解决方案。
6. 个人经验与最终建议
6.1 我的实际心得体会
Docker 磁盘清理这件事,操作层面其实不难,难的是判断——哪些空间该回收,哪些空间是服务的“救命稻草”。我见过有人图省事直接rm -rf /var/lib/docker,然后全站服务不可用,这种代价根本不是几十 GB 空间能比的。
正确的心态是:先用docker system df+du搞清事实,再用prune系列命令按层级操作,最后用日志轮转和定时任务守住底线。这套流程放在任何规模的 Docker 环境里都跑得通,不会出错。
另外还有一个小技巧值得分享:如果一台机器上 Docker 长期不用,但一直占着几十 GB 空间,命令docker system prune -a --volumes清不干净时,可以考虑直接把 Docker 的>