
磁盘又快满了。如果你看到系统监控里那个磁盘使用率的红色曲线一路爬升或者执行命令时突然报出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 Cachedocker 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然后以管理员身份打开 PowerShellOptimize-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-depth1 / | 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.size50G ] }最后一行overlay2.size50G为容器可写层设置了一个总容量上限限流当可写层数据超过 50GB 时Docker 会拒绝继续写入。这在多人共用一台 Docker 主机时非常实用能避免某个用户的容器把磁盘打满导致所有服务崩溃。5.2 定时任务自动清理如果没有停机窗口可以配置一个定时清理脚本比如每天凌晨 2 点执行只清理悬空资源的任务0 2 * * * /usr/bin/docker system prune -f --filter until48h /dev/null 21--filter until48h只清理超过 48 小时未被使用的资源风险更低。需要注意docker system prune默认不会清理数据卷所以这个脚本不会误删卷数据适合作为日常维护任务。每周可以手动执行一次更大的清理按需搭配--volumes。5.3 监控预警配置磁盘使用率的监控告警非常有必要。可以在节点上安装node_exporterPrometheus 生态搜集磁盘指标设置一个 80% 的告警阈值。至少也要在 cron 里挂一个简单的磁盘使用率检测脚本#!/bin/bash THRESHOLD80 USAGE$(df / | awk NR2 {print $5} | sed s/%//) if [ $USAGE -gt $THRESHOLD ]; then echo Disk usage is above $THRESHOLD%: ${USAGE}% | mail -s Disk Alert youremail.com fi磁盘告警的价值在于所有清理命令都是有损的最稳定的状态是让磁盘占用停留在可控范围内而不是每个月都做一次“大扫除”。通过监控预警把问题消灭在早期是更优雅的解决方案。6. 个人经验与最终建议6.1 我的实际心得体会Docker 磁盘清理这件事操作层面其实不难难的是判断——哪些空间该回收哪些空间是服务的“救命稻草”。我见过有人图省事直接rm -rf /var/lib/docker然后全站服务不可用这种代价根本不是几十 GB 空间能比的。正确的心态是先用docker system dfdu搞清事实再用prune系列命令按层级操作最后用日志轮转和定时任务守住底线。这套流程放在任何规模的 Docker 环境里都跑得通不会出错。另外还有一个小技巧值得分享如果一台机器上 Docker 长期不用但一直占着几十 GB 空间命令docker system prune -a --volumes清不干净时可以考虑直接把 Docker 的>