Docker镜像层太多导致云服务器磁盘爆了怎么清理不影响运行
Docker镜像层太多把云服务器磁盘打满,清理时别碰错地方
线上机器磁盘爆掉,Docker 经常是第一嫌疑人。尤其是经常发版、频繁构建镜像、跑 CI/CD、测试环境反复拉镜像的服务器,/var/lib/docker 下面的 overlay2、image、containers、buildkit 很容易堆到几十 GB,甚至把系统盘直接吃满。
实际使用中发现,很多人看到 overlay2 很大,第一反应是进 /var/lib/docker/overlay2 手动 rm。这个操作不建议做。overlay2 里面是 Docker 的存储层,目录名又长又乱,看起来像垃圾,但它和镜像层、容器读写层都有映射关系。手动删,轻则容器启动失败,重则 Docker metadata 和实际文件不一致,后面清理更麻烦。
正确处理思路是:先确认是谁占空间,再用 Docker 自己的命令清理未使用对象。运行中的容器尽量不要动,日志、停止的容器、未使用镜像、build cache 这些才是主要清理对象。
先看磁盘是不是真的被 Docker 吃掉
先看系统盘使用情况:
df -h
如果 / 或 /var 已经 90% 以上,就要尽快处理。很多云服务器默认 Docker 数据目录在 /var/lib/docker,系统盘只有 40G、50G 的机器,发几次版就可能满。
再看 Docker 目录大小:
du -sh /var/lib/docker
如果这个目录已经几十 GB,基本可以继续往 Docker 查。
Docker 自带的空间统计更有用:
docker system df
想看更细:
docker system df -v
这里会列出 Images、Containers、Local Volumes、Build Cache 的占用。实际排查时重点看 RECLAIMABLE,也就是 Docker 判断可以回收的空间。
常见占用来源怎么看
Images 大,通常是历史镜像太多。比如应用每次发布都生成 app:20240501、app:20240502、app:20240503,旧版本没人用但一直留着。
Containers 大,常见是退出的容器堆积,或者容器日志太大。很多业务默认 json-file 日志没有限制,stdout 打得多,一个容器日志能涨到 10G。
Build Cache 大,多见于本机构建镜像。Docker BuildKit 会缓存中间层,加快下次构建,但服务器磁盘小的时候,这部分会非常显眼。
Local Volumes 大,要谨慎。Volume 里可能是数据库数据、上传文件、业务缓存,不能看到“未使用”就直接删,除非确认不再需要。
不影响运行容器的清理命令
清理前先看当前运行容器:
docker ps
再看所有容器,包括已经退出的:
docker ps -a
已经退出的容器,如果不是要保留现场,通常可以删。删除停止容器不会影响正在运行的容器:
docker container prune
执行时 Docker 会提示确认。这个命令只删除 stopped containers,不会停掉 running containers。
清理 dangling images
dangling image 通常显示为 ,多半是构建过程中遗留下来的无标签镜像层。清理它一般比较安全:
docker image prune
这个命令只清理 dangling images,不会删除正在被容器使用的镜像。
如果想看有哪些:
docker images -f dangling=true
多说一句, 不一定都是废物,但在线上普通应用发布场景里,绝大多数都可以通过 docker image prune 处理。
清理所有未被容器使用的镜像
如果镜像堆积很多,dangling 清理完还是没降多少,可以用:
docker image prune -a
这个命令会删除所有没有被任何容器使用的镜像。注意这里说的是“没有被容器使用”,不是“没有运行”。如果一个停止状态的容器还引用某个镜像,这个镜像一般不会被删。
这条命令不影响正在运行的容器,但会影响后续回滚和快速重启。比如你本机留着 app:v1、app:v2、app:v3,当前容器跑的是 app:v3,v1 和 v2 没有任何容器引用,那么 docker image prune -a 可能会把 v1、v2 清掉。后面要回滚就得重新 pull。
生产环境建议先列一下镜像:
docker images
确认镜像仓库可用、网络正常,再执行 docker image prune -a。跨境拉镜像的机器尤其要注意,海外仓库访问慢的时候,删完再拉可能会耽误恢复时间。
清理 Build Cache
本机经常 docker build 的机器,Build Cache 很容易占空间。看缓存:
docker builder du
清理未使用构建缓存:
docker builder prune
清理更彻底:
docker builder prune -a
-a 会删除更多缓存层,不会直接影响正在运行的容器,但会让下一次构建变慢。CI 机器磁盘紧张时,这条命令很常用。
docker system prune 能不能用
docker system prune 是很多人搜索到的清理命令,它会删除停止的容器、未使用网络、dangling images、build cache。默认不会删 volume。
docker system prune
如果加 -a:
docker system prune -a
它会删除所有未被容器使用的镜像,效果接近 container prune、network prune、image prune -a、builder prune 的组合。
如果再加 --volumes:
docker system prune -a --volumes
这个就要非常小心。Volume 可能存数据库、Redis 持久化、MinIO 数据、用户上传文件。很多事故不是清镜像出问题,而是把 volume 一起 prune 了。
线上机器不建议随手执行带 --volumes 的 prune。除非已经确认 volume 都是临时数据,或者已经做了快照、备份。
容器日志经常是隐藏大户
Docker 默认 json-file 日志在这个目录:
/var/lib/docker/containers/
查一下日志大小:
find /var/lib/docker/containers -name "*-json.log" -exec du -h {} \; | sort -h
如果发现某个 json.log 几 GB,清理它不需要删除容器。可以直接截断:
truncate -s 0 /var/lib/docker/containers/
这里补充一点,截断日志文件一般不会影响容器运行,但会丢失当前已有日志。如果线上还需要排查问题,先保存一份:
cp /var/lib/docker/containers/
更好的处理是限制日志大小。修改或创建 Docker daemon 配置:
vim /etc/docker/daemon.json
写入:
{"log-driver":"json-file","log-opts":{"max-size":"100m","max-file":"3"}}
重启 Docker:
systemctl restart docker
注意,重启 Docker 可能影响容器,取决于 Docker 版本、容器 restart policy、业务部署方式。生产环境别在业务高峰期直接重启。已经运行的容器有时需要重新创建才会应用新的日志参数。
不要手动删除 overlay2
overlay2 占用大很正常,因为它承载镜像层和容器层。看到下面这种目录很大,不代表它就是孤儿文件:
/var/lib/docker/overlay2
手动删 overlay2 的风险是 Docker 自己的 metadata 还认为这些层存在,结果容器启动时报错,镜像删除时报错,甚至 docker system prune 也处理不了。
遇到 overlay2 特别大,还是从这些命令入手:
docker system df -v
docker container prune
docker image prune -a
docker builder prune -a
如果 Docker 已经因为磁盘满导致命令执行异常,可以先清理系统日志或容器 json.log,释放几百 MB 到 1GB 空间,让 Docker daemon 能正常工作,再用 Docker 命令清理。
磁盘已经 100%,Docker 命令卡住时怎么救
磁盘满到 100% 时,经常出现 docker ps 卡住、docker build 失败、容器写文件报 no space left on device。这个时候不要急着删镜像目录,先从相对安全的地方挤出空间。
看 journal 日志:
journalctl --disk-usage
清理到最近 3 天:
journalctl --vacuum-time=3d
或者限制到 500M:
journalctl --vacuum-size=500M
再查大文件:
du -xh /var | sort -h | tail -30
如果定位到 Docker 容器日志,可以用 truncate 先救急。释放出空间后,再执行 Docker prune。
有些机器 /tmp、/var/tmp 也会堆积安装包、临时文件,可以看:
du -sh /tmp /var/tmp
确认没有业务临时文件再清。
线上清理前最好做一次快照
云服务器上处理磁盘问题,快照很有价值。尤其是跑数据库、文件服务、业务数据卷的机器,清理前做系统盘快照和数据盘快照,出问题能回退。
如果机器本身系统盘太小,经常被 Docker 打满,单纯清理只是临时处理。比如 20G 系统盘跑 Docker 构建、镜像仓库缓存、业务容器,压力会很大。实际生产里更建议把 Docker 数据目录放到单独数据盘,或者直接选系统盘、数据盘空间更宽裕的云服务器。
如果你也在找这种适合跑 Docker 服务、跨境业务或游戏业务的云服务器,可以看看129云。比如美国高防-B型是 4C 4G、80G SSD、75Mbps 峰值带宽,带 200G 防御,适合有 DDoS 风险的业务节点;香港 CN2 大宽带-B型带 50G SSD 数据盘和 30Mbps 精品 CN2,适合对大陆访问延迟比较敏感的服务。需要按业务场景确认线路和配置,也可以直接问客服,热线 400-9177118。
把 Docker 数据目录迁到数据盘
如果系统盘太小,可以把 Docker root dir 迁到数据盘。先看当前目录:
docker info | grep "Docker Root Dir"
常见输出是:
Docker Root Dir: /var/lib/docker
假设新数据盘挂载在 /data,准备把 Docker 数据放到 /data/docker。操作前建议停业务窗口,做快照。
停止 Docker:
systemctl stop docker
同步数据:
rsync -aHAX /var/lib/docker/ /data/docker/
修改 daemon 配置:
vim /etc/docker/daemon.json
加入 data-root:
{"data-root":"/data/docker","log-driver":"json-file","log-opts":{"max-size":"100m","max-file":"3"}}
如果原来 daemon.json 已经有内容,要合并成合法 JSON,不要写两个独立的大括号。
启动 Docker:
systemctl start docker
检查容器是否正常:
docker ps
docker images
确认业务正常后,原来的 /var/lib/docker 不要马上删,可以先改名观察:
mv /var/lib/docker /var/lib/docker.bak
运行一段时间没问题,再清理 bak。这里要注意,如果 /data 没有开机自动挂载,重启后 Docker 可能找不到 data-root,所以 /etc/fstab 要配置好。
清理命令按风险分开用
低风险,适合日常处理:
docker container prune
docker image prune
docker builder prune
truncate -s 0 容器-json.log
中等风险,需要确认镜像仓库和回滚方式:
docker image prune -a
docker system prune -a
docker builder prune -a
高风险,生产环境不要随手执行:
docker system prune -a --volumes
rm -rf /var/lib/docker/overlay2/*
rm -rf /var/lib/docker/volumes/*
尤其是 volume,很多应用看起来是容器化部署,实际数据都在 Docker volume 里。删掉以后容器还能启动,但数据没了,这种恢复起来比镜像丢失麻烦得多。
让磁盘别再反复爆
发布脚本里可以加定期清理,但不要每次发版都无脑 prune -a。比较稳的做法是保留最近几个版本镜像,旧镜像由定时任务清理。
例如只清理 7 天前的 dangling image:
docker image prune -f --filter "until=168h"
清理 7 天前未使用 build cache:
docker builder prune -f --filter "until=168h"
清理停止超过 7 天的容器:
docker container prune -f --filter "until=168h"
日志限制要尽早配置。很多业务本身日志已经进 ELK、Loki、CloudWatch 或其他日志系统,容器本地 json-file 没必要无限保存。
还有一个容易忽略的地方:镜像体积。Java 应用一个镜像 1.5G,保留 10 个版本就是 15G;Node、Python 如果把缓存、源码临时文件、构建产物全塞进去,也会很快膨胀。多阶段构建、清理包管理器缓存、固定基础镜像版本,都能明显减少磁盘压力。
查看单个镜像层:
docker history 镜像名:tag
如果看到某一层特别大,就回 Dockerfile 查对应指令。比如 RUN apt update 后没有清理 apt cache,或者构建目录没删,都会留在镜像层里。
一个线上处理顺序示例
机器报警:系统盘 95%,业务容器还在跑。处理时可以这样走:
df -h
du -sh /var/lib/docker
docker system df -v
如果日志很大,先截断超大 json.log:
find /var/lib/docker/containers -name "*-json.log" -exec du -h {} \; | sort -h
truncate -s 0 /var/lib/docker/containers/容器ID/容器ID-json.log
再清停止容器:
docker container prune
清 dangling image:
docker image prune
看空间有没有回来:
df -h
docker system df
如果还是紧张,确认镜像仓库可拉、回滚不依赖本地旧镜像,再执行:
docker image prune -a
docker builder prune -a
处理完把 daemon.json 的日志限制补上,后面再安排 Docker data-root 迁移或扩容数据盘。