Docker磁盘占用越来越大怎么清理
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 -a 比 docker 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 不手工删。