Docker磁盘占用越来越大怎么清理

Docker跑久了,磁盘变大是很正常的事。镜像一层层叠,容器日志一直写,CI/CD频繁构建还会留下 build cache,业务容器如果挂了 volume,数据也会越积越多。实际使用中发现,很多机器不是业务数据把盘打满,而是 Docker 自己的缓存、日志、旧镜像把系统盘吃完。

先别急着删 /var/lib/docker,尤其不要直接 rm -rf overlay2。Docker 的数据结构不是普通目录堆文件,overlay2、containers、volumes、image 这些目录之间有引用关系,手工硬删很容易把 Docker 搞到不可恢复。正确做法是先看清楚占用,再按类型清。

先确认 Docker 到底占了多少

先看系统盘情况:

df -h

如果 / 分区已经 90% 以上,Docker 又安装在默认路径,那重点看 /var/lib/docker:

du -sh /var/lib/docker

再用 Docker 自带命令看结构化占用:

docker system df

这个命令会把占用拆成 Images、Containers、Local Volumes、Build Cache。一般现场排查时,先看 RECLAIMABLE 这一列,能回收多少大概心里就有数。

想看得更细一点:

docker system df -v

这条会列出具体镜像、容器、volume 的大小。业务环境里建议先执行查看命令,不要上来就 prune,尤其是有数据库、对象存储、MQ 这类容器时,volume 里面可能是真数据。

清理已经停止的容器

很多容器已经退出了,但文件系统层还在。比如发布脚本每次 docker run 一个临时容器,跑完不删,几个月下来能堆出很多 stopped containers。

查看所有容器:

docker ps -a

只看已经退出的容器:

docker ps -a -f status=exited

清理所有停止状态的容器:

docker container prune

这条不会删除正在运行的容器,但会删除所有 stopped containers。线上机器执行前建议看一眼,有些人会把“停止的容器”当作回滚现场保留,不过这个习惯不太好,回滚应该靠镜像 tag 和部署系统,不该靠停在那里的容器。

清理没被使用的镜像

镜像是 Docker 磁盘增长的大头之一。常见情况是每次发布都会拉一个新 tag,比如 app:20250101、app:20250102、app:20250103,旧版本容器早就不用了,镜像还留在本机。

查看镜像:

docker images

清理 dangling images,也就是没有 tag、没有容器引用的中间镜像:

docker image prune

如果要清理所有未被容器使用的镜像,用:

docker image prune -a

这里补充一点,docker image prune -adocker image prune 激进很多。它会删除所有“当前没有容器使用”的镜像,不管有没有 tag。比如你机器上保留了 app:v1.2.0 准备回滚,但当前没有容器基于它运行,这条命令也可能把它删掉。

比较稳的方式是先用:

docker system df -v

看哪些镜像确实没用,再按镜像 ID 删除:

docker rmi 镜像ID

如果是测试机、CI 构建机,镜像没什么保留价值,可以定期跑 docker image prune -a。生产机要看发布和回滚策略,不建议无脑全清。

清理 build cache,CI 机器经常中招

如果机器承担 Docker build,尤其是 Jenkins、GitLab Runner、Drone、自研发布机,build cache 会涨得很快。Dockerfile 每一层都会缓存,构建频率高的时候,几十 GB 很常见。

查看 build cache:

docker builder du

清理无用 build cache:

docker builder prune

清理全部 build cache:

docker builder prune -a

BuildKit 场景也一样适用。实际使用中发现,CI 节点磁盘打满,十次里面有好几次是 build cache 没管控。尤其是多分支、多项目共用一个 runner,构建缓存不限制,系统盘 100G 很快就没了。

如果只想清理超过一定时间的缓存,可以用 filter:

docker builder prune --filter until=168h

这表示清理 168 小时,也就是 7 天以前的缓存。这个方式比较适合构建机做定时任务。

清理 volume 要非常小心

volume 是最容易误删出事故的地方。数据库数据、上传文件、业务状态文件,经常都在 volume 里。Docker 的 prune 命令里,volume 默认不会被 docker system prune 删除,除非显式加 --volumes

查看 volume:

docker volume ls

查看某个 volume 的挂载路径:

docker volume inspect volume名称

查看 volume 实际大小,可以结合挂载路径:

du -sh /var/lib/docker/volumes/volume名称/_data

清理没有被任何容器使用的 volume:

docker volume prune

这条命令只删 dangling volumes,但现场仍然要谨慎。有些容器被删了,volume 还留着,本来是为了后续恢复数据;这时执行 volume prune 就会把这些“孤儿数据”删掉。

多说一句,生产环境里数据库不建议完全依赖匿名 volume。最好使用具名 volume 或者明确的 bind mount,比如挂到 /data/mysql、/data/postgres 这类目录,备份、迁移、监控都更清楚。

容器日志经常被忽略

Docker 默认 json-file 日志驱动,如果没有限制大小,容器 stdout、stderr 会一直写到宿主机磁盘。业务日志量大的容器,单个日志文件几十 GB 并不稀奇。

查看容器日志文件路径:

docker inspect --format='{{.LogPath}}' 容器名或容器ID

查看日志大小:

ls -lh 日志文件路径

如果只是临时救急,可以把日志清空,不删除文件:

truncate -s 0 日志文件路径

注意这里是 truncate,不是 rm。直接 rm 掉日志文件后,Docker 进程可能还持有文件句柄,磁盘空间不一定马上释放,处理起来更麻烦。

更好的方式是在 Docker daemon 层限制日志大小。编辑或创建:

/etc/docker/daemon.json

写入类似配置:

{"log-driver":"json-file","log-opts":{"max-size":"100m","max-file":"3"}}

然后重启 Docker:

systemctl restart docker

这个配置表示单个容器日志文件最大 100MB,最多保留 3 个文件。需要注意,重启 Docker 会影响容器,线上操作要看业务是否有自动恢复、是否允许短暂中断。

如果使用 docker compose,也可以在 compose 文件里给单个服务配置 logging,例如限制 max-size 和 max-file。日志量大的服务,比如 Nginx、网关、爬虫、游戏日志采集服务,最好单独管控。

docker system prune 能清什么

很多人最常用的是:

docker system prune

它会清理停止的容器、未使用的 network、dangling images、build cache。默认不删除未使用的 volume。

更激进一点:

docker system prune -a

这会额外删除所有未被容器使用的镜像。

如果再加 volume:

docker system prune -a --volumes

这条就很危险了,会把未使用的 volume 也清掉。测试环境可以用,生产环境不建议随手执行。尤其是一些业务迁移后暂时停止容器、volume 还保留着的场景,执行完可能就真没了。

清理前可以按这个顺序排查

现场处理磁盘告警时,比较顺的节奏是先看 df -h,确认是不是根分区满;再看 du -sh /var/lib/docker,确认 Docker 是否是主要占用;接着跑 docker system df -v,判断是镜像、容器、volume、build cache 还是日志。

如果 Images 很大,优先清旧镜像;如果 Build Cache 很大,清 builder cache;如果 Containers 很大,看 stopped containers 和容器日志;如果 Local Volumes 很大,不要急着 prune,先确认里面是不是业务数据。

还有一种情况是 docker system df 看起来不大,但 /var/lib/docker 很大,这时通常要重点查容器日志、overlay2 里是否有异常写入,或者有进程持有已删除文件。可以用:

lsof | grep deleted

如果看到某个进程持有超大的 deleted 文件,重启对应进程或容器后空间才会释放。

不要直接删 overlay2

overlay2 目录很容易让人误判。用 du -sh /var/lib/docker/overlay2 一看几十 GB,手就痒想删。这个目录是 Docker 存储驱动的核心,里面包含镜像层和容器可写层,不是普通缓存目录。

正确处理方式是通过 Docker 命令删除容器、镜像、缓存,让 Docker 自己维护引用关系。直接删除 overlay2 可能导致容器启动失败、镜像损坏、Docker daemon 报错,后面修起来比扩容磁盘还麻烦。

如果 overlay2 异常膨胀,先定位是哪一个容器的可写层变大。可以用:

docker ps -s

这个命令会显示容器 writable layer 的大小。某个容器的 SIZE 特别大,通常说明程序把大量数据写进了容器内部,而不是写到挂载目录。

比如应用把上传文件写到了 /app/uploads,但没有挂载 volume;或者日志写到了容器文件系统里的 /var/log/app.log。这种场景下,容器一运行,overlay2 就会越来越大。

业务文件不要写进容器层

容器镜像应该尽量保持不可变,运行时产生的数据放到 volume、bind mount、对象存储或者外部数据库。否则容器层会越来越大,迁移、备份、回滚都不清楚。

典型错误写法是:

docker run -d --name app app:latest

应用启动后把上传文件、导出文件、临时文件都写在容器内部。短期看没问题,跑几个月后,容器 writable layer 可能几十 GB。

更合理的方式是把数据目录挂出来:

docker run -d --name app -v /data/app/uploads:/app/uploads app:latest

这样至少能在宿主机上直接看到数据大小,备份策略也更好做。

定时清理可以做,但别把生产机当测试机

测试环境、构建环境、临时验证环境,可以设置定时清理。比如每天凌晨清理 7 天前的 build cache、停止容器、无用镜像。

示例:

docker container prune -f

docker image prune -a -f --filter until=168h

docker builder prune -a -f --filter until=168h

生产环境不建议直接把 docker system prune -a --volumes -f 放进 crontab。这个命令太猛,出了问题排查空间很小。

更稳的做法是生产机只做日志限制、构建机做缓存清理、发布机控制镜像保留数量。比如每台生产机保留最近 3 到 5 个可回滚版本,老版本由镜像仓库保存,不靠本地硬盘长期存。

系统盘太小也会放大问题

Docker 默认把数据放在 /var/lib/docker,如果服务器系统盘只有 40G 或 50G,稍微跑几个镜像、几个容器,再加日志和缓存,很快就紧张。尤其是游戏服、Web 网关、CI 节点、日志采集节点,对磁盘和网络都有要求,机器规格不能只看 CPU 和内存。

如果业务部署在海外节点,或者需要香港、美国方向的稳定访问,可以按场景选机器。比如香港 CN2 线路适合对大陆访问质量敏感的 Web、API、轻量业务;高防场景要考虑 DDoS 防护和带宽冗余;构建机、镜像分发节点则更看重磁盘、带宽和稳定性。如果你也在找这类云服务器,可以看看129云,像香港CN2大宽带-E型有 16C、16G DDR4 ECC、70G SSD 数据盘、50Mbps 峰值精品 CN2;香港高防-D型提供 200Gbps 单机防御和 350Mbps 峰值带宽,适合有攻击风险的业务入口。需要按业务场景确认配置,也可以直接联系 400-9177118。

把 Docker 数据目录迁到数据盘

如果系统盘已经偏小,或者业务容器本来就多,可以把 Docker data-root 放到数据盘。比如把 Docker 数据目录迁到 /data/docker。

操作前先停 Docker:

systemctl stop docker

同步原数据:

rsync -aHAX /var/lib/docker/ /data/docker/

编辑 daemon 配置:

/etc/docker/daemon.json

加入 data-root:

{"data-root":"/data/docker","log-driver":"json-file","log-opts":{"max-size":"100m","max-file":"3"}}

启动 Docker:

systemctl start docker

确认容器正常后,再考虑处理旧目录。不要刚迁完就删旧数据,至少保留一段时间,确认镜像、容器、volume 都正常。

检查 Docker 当前数据目录:

docker info | grep "Docker Root Dir"

这里如果显示 /data/docker,说明已经切过去了。

docker compose 项目也要清旧资源

docker compose 用久了也会留下不少东西。项目改名、目录改名、服务名调整后,旧 network、旧 container、旧 image 可能还在。

查看 compose 项目:

docker compose ps -a

停止并删除当前 compose 项目的容器和网络:

docker compose down

如果要连镜像一起删:

docker compose down --rmi local

如果要删除 volume:

docker compose down -v

down -v 同样要谨慎,数据库、Redis、MinIO 这类服务如果 volume 在 compose 里定义,执行后数据会被删。实际环境里,带状态服务和无状态服务最好分开管理,不要一个 down -v 把整套环境的数据一起带走。

清理后空间没释放怎么办

清理命令执行完,docker system df 看起来降了,但 df -h 没变化,这种情况多半是文件句柄还被进程占用。

查 deleted 文件:

lsof | grep deleted

如果看到 dockerd、containerd、业务进程持有大文件,可以重启对应容器:

docker restart 容器名

如果是 Docker daemon 自身相关,可以安排窗口重启 Docker:

systemctl restart docker

重启前确认容器 restart policy,避免 Docker起来后业务容器没自动拉起。可以看:

docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' 容器名

日常建议放在配置里,而不是靠人记

日志限制建议直接写进 Docker daemon.json。构建机建议加定时清理 build cache。发布系统建议控制本机镜像保留数量。业务数据建议挂载到清晰的数据目录,不写进容器层。

监控上至少要盯住根分区使用率、/var/lib/docker 占用、容器日志目录、inode 使用率。磁盘没满但 inode 满了也会导致创建文件失败,Docker 拉镜像、写日志、启动容器都会异常。

查看 inode:

df -ih

如果 inode 使用率异常高,通常是大量小文件导致的,比如缓存目录、临时文件、某些日志切分策略不合理。Docker 只是表现出启动失败,真正原因还要回到文件系统层面查。

一台机器上的常用排查命令

df -h

df -ih

du -sh /var/lib/docker

docker system df

docker system df -v

docker ps -a

docker ps -s

docker builder du

docker volume ls

docker inspect --format='{{.LogPath}}' 容器名

lsof | grep deleted

临时救急通常从日志和 stopped containers 下手,风险比较低;镜像清理要看回滚需求;volume 清理要看数据归属;overlay2 不手工删。