
Docker跑了大半年最头疼的不是容器编排而是磁盘被不知不觉塞满。明明镜像也不多容器也就二十几个df -h一看可用空间就剩几个GDocker Desktop的虚拟磁盘文件动辄几十上百G连带着整个电脑都开始卡顿。这篇文章就聊聊我踩过的坑和整理出来的一套清理思路从最基础的docker system df排查到日志限制、构建缓存、卷清理再到daemon.json的长期防护配置一条龙说清楚。1. 先搞清楚磁盘空间到底被谁吃掉了很多新手一上来就无脑docker system prune -a其实这是最大的误区。清理之前先花两分钟搞清楚空间去向才能对症下药避免误删还在用的镜像和容器数据。1.1 Docker磁盘占用的四大来源先给你们画一张心理地图——Docker的磁盘占用其实分布在四个层面互相独立又有交叉镜像层文件每拉取一个镜像Docker都会把它按照分层结构存储。同一个基础镜像的不同变体比如Ubuntu 20.04和22.04每层都要占一份空间。这是最大头的占用来源。容器可写层容器启动后对文件系统的所有写操作都会落在可写层里容器删除后这部分才释放。如果容器长期运行还在不断写入数据比如数据库容器可写层会越滚越大。数据卷docker volume是独立于容器生命周期的持久化存储删除容器并不会删除卷。很多人把数据库数据直接放在容器里或者用匿名卷存了一堆临时文件容器删了卷还在空间就白白浪费。构建缓存与日志docker build产生的中间层镜像以及容器写到JSON文件里的标准输出日志。前者是隐藏的幽灵后者是无底洞——尤其是生产环境里打印日志密集的容器单日日志就能冲到几个G。1.2 三步定位空间占用大户排查阶段不需要猜Docker自带命令直接用起来docker system df这个命令会输出一张表格分别统计镜像IMAGES、容器CONTAINERS、卷LOCAL VOLUMES、构建缓存BUILD CACHE的占用情况。我自己的服务器上曾经跑出来过镜像18G、容器可写层6G、缓存35G的恐怖数据问题一目了然。想看得更细加上-v参数docker system df -v这个命令会列出每一个镜像、容器、卷的详细占用。比如你会发现某个打满版本的镜像占了好几G某个容器可写层已经膨胀到2G却没在写数据。定位到具体对象之后清理计划就非常清晰了。如果你习惯用得多一些的排查方式还可以直接去Docker根目录看物理文件sudo du -sh /var/lib/docker/*/var/lib/docker下overlay2、containers、volumes三个目录通常是大头分别对应镜像层、容器写入和卷数据。这套方法适合Linux服务器Docker Desktop用户则要注意去看虚拟磁盘文件在哪。2. 最常用的清理三板斧定位清楚之后就可以上清理命令了。我总结成三板斧一键系统清理、按需精准清理、定期深度清理。2.1 docker system prune 快速清场日常用的最多的就是docker system prune它的作用是把“已经没用”的资源一次性回收docker system prune默认参数下会清理已停止的容器未被任何容器使用的网络悬空镜像dangling images即标签为none的镜像构建缓存加上-a参数会连“没有被容器引用的镜像”一起删docker system prune -a注意这个操作的风险等级完全不同。prune只动悬空镜像prune -a会把所有不再被容器使用的镜像全部删除哪怕是有名字、有标签的正式版本。比如你本地拉了个nginx:latest现在没有容器在用执行prune -a之后这个镜像就直接没了下次要用还得重新拉取。2.2 按类型精准清理镜像、容器、卷、缓存三板斧的第二招是局部精准清理按资源类型分别操作清理悬空镜像docker image prune悬空镜像指那些没有标签、只占用空间的none镜像。它们通常是重新构建时留下的旧镜像层不删白不删。如果想清理所有未被引用的镜像用docker image prune -a这里我个人建议构建节点可以放心加-a生产环境的机器还是先看一眼再动手最好加个过滤条件docker image prune -a --filter until72h意思是只清理72小时前创建且未被引用的镜像最近几天构建的版本还在保护期内误删概率大大降低。清理停止的容器docker container prune这个命令会删除所有处于exited状态的容器比如你调试代码时反复启动又停掉的一堆临时容器。加-f可以不经过确认直接执行。清理无用数据卷docker volume prune这是很多人最容易忽略的一块。Docker在docker run -v时如果不指定命名卷会生成一堆匿名卷容器删除后它们原地不动长期积累下来体积非常可观。volume prune会把没有被任何容器引用的卷全部清空——操作前务必确认没有重要数据。清理构建缓存docker builder prune构建缓存通常是最意外的空间凶手。我之前构建一个前端镜像的流水线缓存峰值竟然干到了20G以上。如果docker system df显示BUILD CACHE占用异常高直接执行docker builder prune -a -f这个命令是清空所有构建缓存保留结果是绝对安全代价只是下次构建会慢一点。2.3 清理命令的适用场景与代价我用一个表格把这几个命令的使用场景和代价捋清楚方便你们按需选择场景推荐命令风险等级注意事项日常快速清理docker system prune -f低不停机不删镜像可放心执行一口气回收所有未用镜像docker system prune -a -f中所有未运行容器的镜像都会被删需要重新拉取清理悬挂镜像docker image prune -f低只清理none的镜像批量删除停止的容器docker container prune -f低不会动运行中的容器但临时容器数据会丢清理未引用的卷docker volume prune高风险删除所有未被使用的数据卷不可恢复清空构建缓存docker builder prune -a -f低只是构建变慢不影响运行容器经验之谈服务器上别随便执行docker system prune -a至少要加一个--filter until24h保护最近的镜像开发机能扛得住直接-a全清也没关系反正拉取很快。数据卷的清理必须谨慎再谨慎数据库容器停掉之后卷并不会自动消失volume prune一下可能把备份也带走。3. 容易被忽视的深度清理三板斧只能解决表面问题真正吃满磁盘的往往在这几个被忽视的角落容器日志、构建缓存残留、以及镜像层叠加造成的重复空间。3.1 日志文件才是隐藏杀手这是我认为Docker磁盘清理中价值最高的一块——容器日志。标准输出的日志会被Docker写入宿主机默认JSON格式每行日志在/var/lib/docker/containers/container_id/目录下以container_id-json.log文件存在。我见过单容器日志文件暴涨到15G的真实案例排查半天发现只是个业务服务在debug级别下疯狂打印循环输出。处理办法是直接清空日志文件注意不是删除文件——删除后Docker还会继续写甚至可能因为句柄残留导致空间不释放truncate -s 0 /var/lib/docker/containers/*/*-json.log也可以先定位一下日志占用的最大值sudo du -sh /var/lib/docker/containers/*/想做得优雅一点还可以用logrotate定期切割/var/lib/docker/containers/*/*.log { daily rotate 7 copytruncate compress missingok notifempty }但logrotate方案配置起来有点麻烦我更推荐在Docker守护进程级别直接限制日志大小这个放到下一部分详细说。3.2 构建缓存和buildkit缓存清理前面提到docker builder prune能清理构建缓存但这一块实际上还有更细的层次。Docker 23.0之后默认使用BuildKit构建它的缓存分为两大部分一层是构建过程中产生的中间层一层是挂载缓存RUN --mounttypecache。中间层在prune时会被清掉但挂载缓存有时候会以单独的缓存实体留在磁盘上。查看当前构建缓存占用docker system df docker du -sh /var/lib/docker/buildkitBuildKit默认存储在/var/lib/docker/buildkit目录下如果目录体积巨大超过10G说明历史构建的缓存没有清理过。可以手动执行docker builder prune --filter typeexec.cachemount这个命令专门清理RUN --mounttypecache创建的挂载缓存对依赖包缓存、编译缓存这类内容非常有效。还有一种情况docker build时使用了--cache-from和--cache-to导出的缓存它们会存到Docker Hub或本地Registry表面上不占本机空间但拉取下来再用的时候又会占据新的空间。所以本地构建频率高的机器定期docker builder prune -a -f几乎是强制性的。3.3 宿主机层面的善后处理Docker清理完之后宿主机磁盘空间不一定立刻恢复原因在于overlay2目录的镜像层虽然被删掉了但存储驱动不一定立即回收磁盘块。某些情况下还需要做一些善后处理。第一步确认空间是否真的有释放df -h如果df显示的Avail空间没有明显变化先别急着重启试试sudo fstrim -v /var/lib/dockerfstrim会向SSD设备发送TRIM指令配合存储驱动的瘦身回收机制把已经被标记为删除的块真正返还给底层文件系统。这条命令对ext4、xfs配合SSD的场景有效机械硬盘上没用但至少无害。第二步如果宿主机上还有残留的Docker临时文件在/var/lib/docker/tmp里可以手动清理sudo rm -rf /var/lib/docker/tmp/*仓库里还可能出现某些因中断操作留下的临时目录直接删掉不会影响Docker运行。第三步确认/var/lib/docker/overlay2目录里没有大量无用的孤儿目录。有些场景下容器删除了但旧的overlay挂载点没有卸载导致目录残留。这时候要么重启Docker进程要么执行sudo systemctl restart docker重启Docker会让所有容器发生一次短暂中断如果环境不允许停服可以跳过这一步等维护窗口期再处理。4. 从源头控制让磁盘不再膨胀会清理只是及格水平会设置各种限制、让Docker不再疯狂吃空间才是高手操作。这一部分说说怎么从配置层面堵住洞口。4.1 daemon.json里的日志与限制配置Docker守护进程的配置文件是/etc/docker/daemon.json没有这个文件就新建一个重启Docker后生效。我目前在生产环境上用的核心配置是这样的{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, storage-driver: overlay2, storage-opt: [ overlay2.size50G ] }重点看前两项max-size限制单个日志文件最大10MBmax-file限制保留3个日志文件也就是说单个容器最多保留30MB日志写满了会自动滚动覆盖旧的。这个配置能彻底解决日志无限膨胀的问题前提是业务上不能依赖历史日志做排障——反正我真实的经验是日常排障最多看到最近几小时到几天的日志太旧的也没啥用。第三项storage-opt是针对overlay2存储驱动的容器可写层大小限制单位是G或B。设置后每个容器可写层最多占用50GB超出会触发写失败。这个限制能防止某个容器因为写满数据而拖垮整个宿主机的磁盘适合多租户场景。注意修改daemon.json之后要执行sudo systemctl daemon-reload sudo systemctl restart docker重启Docker会使所有运行中的容器中断这个操作最好安排在维护窗口期。而且改日志配置只对新创建的容器生效已经存在的容器还是要重建或者手动处理日志文件。4.2 日常运维习惯与自动化清理配置之外平时多注意一些运维细节能省去很多半夜救火的痛苦定期执行清理脚本我习惯在crontab里挂一个每周清理任务0 2 * * 1 docker container prune -f --filter until72h docker image prune -f --filter until168h docker builder prune -f这个任务是每周一凌晨两点执行清理停止时间超过72小时的容器、清理挂起时间超过168小时的悬空镜像、清理构建缓存。这些参数不会删除还在使用的镜像也不会误删正在运行的东西安全系数比较高。提示用crontab跑Docker命令时一定要写清Docker的完整路径或者先export PATH$PATH:/usr/bin不然cron环境里经常找不到docker命令。镜像管理规范拉镜像、建镜像的时候也留个心眼同一个项目尽量复用基础镜像层不要每个镜像都独立拉一个完整OS层省下的是真金白银的磁盘。给镜像打标签时注意清理旧版本避免每个旧tag都在本地留一份。docker build时善用.dockerignore把node_modules、target这类编译产物排除在构建上下文之外不然每次构建都把整个项目复制一遍缓存和网络层都白占空间。私有仓库定期清理不再使用的镜像Tag本地也同步清理。使用代理或镜像加速源在国内环境下拉取官方镜像经常很慢慢的就要反复重试重试的文件断断续续也会在本地产生大量临时镜像层。改用能稳定访问的镜像加速源之后拉取过程更稳定本地残留的临时文件少得多磁盘压力也小一些。5. 常见问题与排查技巧实录理论说完到了实操的疑难杂症环节。这些内容几乎都是我从各种翻车现场总结出来的每一条都值得备份收藏。5.1 清理完空间没释放三个可能原因清理命令执行完之后df -h发现空间没有明显回升这个现象很常见。我总结三个高概率原因原因一存储驱动未回收overlay2在删除镜像层之后需要配合fstrim或者重启Docker才能把块真正释放特别是使用ext4和XFS文件系统时。解决方案就是前面提到的sudo fstrim -v /var/lib/docker。原因二日志文件被进程占用即使truncate -s 0清了日志文件但如果容器还在运行中文件句柄一直被Docker持有磁盘上的blocks可能还不会被释放。可以先停掉容器再清理或者直接用/dev/null来覆盖日志文件sudo sh -c cat /dev/null /var/lib/docker/containers/container_id/container_id-json.log原因三Deleted文件仍被占用用lsof L1查找被标记为deleted但仍然打开的文件sudo lsof L1 | grep deleted如果发现Docker相关进程仍在写某个已删除的文件重启该容器或重启Docker后空间一般就能释放。5.2 悬空镜像清理不掉检查容器依赖docker image prune -f执行完以后再次docker images还看到一堆none镜像通常是某个停止的容器还引用着这些镜像层。可以先清理停止的容器再清理悬空镜像docker container prune -f docker image prune -f还有种情况是构建缓存导致的none镜像这类记录不会在docker images列表里显示却实实在在占用空间。处理方式是docker builder prune -a -f或者干脆docker image prune -a --filter until12h把最近12小时之前的无引用镜像全部清走。5.3 Docker Desktop 的虚拟磁盘文件膨胀不少同学用的不是Linux服务器而是Mac或Windows上的Docker Desktop这类工具本质是跑在虚拟机里的Docker磁盘占用多数时候都集中在那个虚拟磁盘文件上。你执行了docker system prune发现虚拟磁盘镜像文件大小没有变化需要额外处理。在Docker Desktop里清理完成后可以执行docker system prune -a --volumes然后在Docker Desktop界面中找到Dashboard的Troubleshoot或者Resources面板一般会有“Clean / Purge data”或者“Disk utilization”的选项点击之后Docker会重建精简后的磁盘镜像。或者手动打开虚拟磁盘管理工具执行一次磁盘压缩对应的就是macOS上的hdiutil / Windows上的Optimize-VHD。这个操作能回收的空间非常可观经常有人一口气释放掉20到30G。5.4 排查技巧汇总速查表症状原因解决方案df -h显示空间没释放存储驱动未做TRIMsudo fstrim -v /var/lib/docker或重启Docker单个容器日志文件巨大未设置日志滚动上限修改daemon.json的log-opts重建容器none镜像清理不掉停止的容器仍引用镜像先删容器再删镜像BUILD CACHE占用超高构建频繁或buildkit缓存积累docker builder prune -a -fDocker Desktop磁盘文件不减虚拟机磁盘未压缩在Dashboard内清理并压缩磁盘overlay2目录特别大镜像层叠加太多用docker system prune -a清理镜像配合fstrim回收容器停止后卷仍然占用匿名卷未清理docker volume prune确认无数据后5.5 最后再分享一个小操作习惯清理Docker之余也顺手看一眼宿主机的其他角落有时候能发现比Docker更占空间的元凶——比如node_modules、构建产物、日志压缩包之类的。我自己的习惯是每月跑一次ncdu或者du -sh /*把宿主机上所有大目录拉出来排查一遍对照Docker内部资源一起做整体减法。还有有任何重要数据操作前先做备份。尤其是执行docker volume prune之前先想清楚现在到底有哪些卷在跑数据。如果拿不准宁可多检查几遍也别为了省事把自己坑了。模棱两可的时候不要用-a不要用--volumes精确定位后再动手。清理这种事情最怕的不是清理不干净而是删了不该删的。