Docker镜像越来越大磁盘撑不住该怎么清理
Docker镜像越来越大,磁盘撑不住时先别急着删容器
线上机器磁盘告警,很多人第一反应是进到业务目录里找大文件,删日志、删临时包。实际使用中发现,跑 Docker 的机器,磁盘被吃满的源头经常不在业务目录,而是在 Docker 自己的数据目录里。
默认情况下,Docker 的数据一般在 /var/lib/docker。镜像、容器可写层、volume、build cache、日志,基本都堆在这里。机器如果只有 40G、50G、60G SSD,跑几轮 CI/CD,再加上服务日志没限制,很容易把盘打满。
先看一眼整体占用,不要上来就 rm -rf:
docker system df
这个命令会把 Images、Containers、Local Volumes、Build Cache 的占用列出来。现场排障时,它比 du -sh /var/lib/docker/* 更直观。比如看到 Build Cache 20G、Images 15G、Volumes 2G,清理方向就很明确,不用在业务目录里乱翻。
先确认磁盘到底被 Docker 哪块吃掉了
常用排查顺序是这样走的。不是为了形式化,而是为了避免删错东西。
看 Docker 总占用
docker system df
如果想看得更细一点:
docker system df -v
-v 会列出每个镜像、容器、volume 的占用情况。这里补充一点,镜像显示的 SIZE 不完全等于真实新增占用,因为镜像层之间会复用。看到多个镜像都是 1GB,不代表它们一定各自占了 1GB。
看 Docker 数据目录
du -h --max-depth=1 /var/lib/docker | sort -h
常见比较大的目录有:
/var/lib/docker/overlay2:镜像层和容器可写层,最常见的大头。
/var/lib/docker/containers:容器日志经常在这里膨胀。
/var/lib/docker/volumes:业务数据卷,不能随便删。
/var/lib/docker/buildkit:BuildKit 构建缓存,CI 机器上很常见。
看容器日志是不是爆了
有些机器镜像其实不大,真正把盘吃满的是 JSON 日志。Docker 默认使用 json-file 日志驱动,如果没有限制大小,容器不停输出日志,单个日志文件几十 GB 都见过。
查看容器日志文件大小:
find /var/lib/docker/containers -name "*-json.log" -exec ls -lh {} \; | sort -k 5 -h
如果看到某个 *-json.log 特别大,不建议直接删文件,更稳的处理是清空内容:
truncate -s 0 /var/lib/docker/containers/容器ID/容器ID-json.log
清空后容器一般不用重启。多说一句,日志被打爆通常说明应用日志级别、采集链路、Docker 日志轮转都没管好,单靠手工清理只能救急。
能安全清理的 Docker 对象
磁盘紧张时,最怕的不是删不掉,而是删得太猛。Docker 里有些东西删了只是重新拉镜像,有些删了业务数据就没了。
清理停止状态的容器
停止的容器会保留可写层,占用不一定大,但数量多了也烦。清理命令:
docker container prune
它只清理 stopped containers,不会动正在运行的容器。上线发布频繁的机器,经常能清出一批历史容器。
清理悬空镜像
构建镜像后,经常会看到 这种镜像,也叫 dangling image。清理命令:
docker image prune
这个命令相对温和,只删没有 tag、没有被容器引用的镜像层。
清理未被使用的镜像
如果确认历史版本镜像不需要保留,可以用:
docker image prune -a
这里要谨慎。-a 会删除所有未被容器使用的镜像,不只是 dangling image。比如机器上保留了昨天、前天的镜像用于快速回滚,但当前没有容器引用它们,执行后就会被删掉。等真要回滚时,只能重新从 registry 拉。
在内网 registry 慢、跨境拉取慢、或者生产环境不希望临时拉镜像的场景,不建议无脑定时执行 docker image prune -a。
清理 Build Cache
CI/CD 构建机最容易被 Build Cache 撑爆。检查:
docker builder du
清理 BuildKit 缓存:
docker builder prune
清得更彻底:
docker builder prune -a
如果每天构建很多次,Build Cache 可能比业务镜像还大。实际使用中发现,开发测试机和构建机上,docker builder prune 的收益经常比删镜像还明显。
清理未使用的网络
Docker network 一般不怎么占磁盘,但环境混乱时可以顺手清一下:
docker network prune
这个影响通常不大,但生产机器上仍然建议先看 docker network ls,确认不是业务手工创建的网络。
volume 不要随便 prune
docker volume prune 是事故高发命令。它会删除没有被容器使用的 volume。听起来安全,但很多业务会出现这种情况:容器临时停掉了,volume 暂时没有挂载;或者旧容器删了,新容器还没拉起来。这个时候执行 prune,数据卷可能直接没了。
查看 volume:
docker volume ls
查看某个 volume 详细信息:
docker volume inspect volume_name
如果 volume 里放的是 MySQL、PostgreSQL、Redis、MinIO、Elasticsearch 数据,清理前必须确认备份。测试环境可以激进一点,生产环境不要把 docker volume prune 写进通用清理脚本。
docker system prune 该怎么用
docker system prune 是很多人最熟的清理命令,但它也最容易被误用。
默认执行:
docker system prune
它会删除 stopped containers、unused networks、dangling images、build cache。
加 -a:
docker system prune -a
会额外删除所有未被使用的镜像。这个在测试机上很爽,在生产机器上要看回滚策略。
加 --volumes:
docker system prune -a --volumes
会连未使用 volume 一起删。这个命令不建议在生产环境直接敲,除非已经确认所有 volume 都不是业务数据,或者数据已经完整备份。
现场一般更倾向于分开清理:先看 docker system df -v,再按容器、镜像、build cache、日志分别处理。虽然慢一点,但可控。
给 Docker 日志加限制,别等爆盘再处理
清理只能解决当下,日志限制才是长期要配的东西。Docker 默认的 json-file 日志如果不限制,应用一旦 DEBUG 日志打开,磁盘会被持续写满。
可以在 /etc/docker/daemon.json 里加配置:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }
意思是每个容器日志文件最大 100MB,最多保留 3 个文件。单容器最多大约 300MB。这个值不是固定标准,要看业务日志量和排障需求。普通 Web 服务可以从 100m、3 开始;高并发网关、游戏服、DDoS 防护相关日志如果写得很密,建议接入日志采集系统,不要全压在本地 Docker 日志里。
修改后重启 Docker:
systemctl restart docker
注意,daemon 配置通常只对新创建的容器生效。老容器要重新创建才能应用新的日志限制。用 Compose 的话就是重新 docker compose up -d。
镜像为什么越构建越大
除了机器上历史镜像多,单个镜像本身也可能越来越大。常见原因是 Dockerfile 写法不干净。
RUN 分层导致缓存文件留在镜像里
比如这样写:
RUN apt-get update
RUN apt-get install -y curl vim
RUN rm -rf /var/lib/apt/lists/*
看起来最后清理了 apt 缓存,但前面的层已经把缓存写进镜像层了。更好的写法是放在同一个 RUN 里:
RUN apt-get update && apt-get install -y curl vim && rm -rf /var/lib/apt/lists/*
镜像层不是普通文件系统的覆盖那么简单。上一层写进去的内容,即使下一层删除,镜像历史层里仍然可能占空间。
把构建依赖带进运行镜像
Go、Node.js、Java、Python 项目都容易出现这个问题。编译时需要一堆依赖,运行时其实只需要产物和少量 runtime,但 Dockerfile 直接从构建环境跑到生产环境,镜像自然越来越大。
这种场景建议用 multi-stage build。比如 Go 项目,前一阶段用 golang 镜像编译,后一阶段用 alpine 或 distroless 放二进制。几百 MB 的镜像压到几十 MB 很常见。
COPY . . 把不该进镜像的东西打进去了
这个也很常见。项目根目录有 node_modules、.git、测试数据、历史压缩包,本地一构建全进镜像。
加 .dockerignore 很关键。常见内容:
.git
node_modules
logs
tmp
*.tar.gz
coverage
.env
这里补充一点,.env 不只是占空间问题,还涉及敏感信息。镜像一旦推到 registry,泄露面就变大了。
清理策略要区分机器角色
不同机器清理方式不一样。把生产机、测试机、构建机都用同一条清理命令,很容易出问题。
生产机器
生产机器重点是稳。可以清 stopped containers、dangling images、过大的容器日志、确认无用的 build cache。对 docker image prune -a 和 docker volume prune 要谨慎。
生产环境一般建议保留至少一个可回滚版本的镜像。比如当前运行 app:v20250120,保留 app:v20250119,遇到新版本异常时可以快速切回。磁盘只有 50G 的机器,就不能无限留历史镜像,保留策略要跟发布频率匹配。
测试机器
测试机器可以激进一些。镜像能重新构建,容器能重新拉起,清理成本低。可以定时执行:
docker system prune -af
如果确认 volume 也都是临时数据,再考虑加 --volumes。但最好不要把这条命令复制到生产机器。
构建机器
构建机器最大的问题通常是 Build Cache 和中间镜像。可以定期跑:
docker builder prune -af --filter "until=168h"
这个表示清理 168 小时以前的构建缓存,也就是 7 天前的缓存。比每次全清更舒服,因为近期缓存还能加速构建。
镜像清理也可以按时间过滤:
docker image prune -af --filter "until=168h"
实际场景里,构建机保留 3 到 7 天缓存比较常见。保留太短,构建变慢;保留太长,磁盘很快报警。
磁盘太小也别硬扛,业务机器选型要留余量
Docker 机器的磁盘不能只按业务包大小算。比如一个 Java 服务 jar 包 150MB,镜像可能 600MB;保留 5 个版本就是 3GB;再加基础镜像层、容器日志、临时文件、监控采集、系统日志,很快就上 10GB。多个服务混部时,50G 磁盘并不宽裕。
如果是建站、轻量 API、企业后台这类场景,单机跑 Docker 没问题,但磁盘和带宽要一起看。比如内蒙电信-C型有 8C、8G DDR4 ECC、60G SSD、50Mbps 峰值带宽,适合对成本敏感、又希望线路稳定的建站和中小业务。如果你也在找这种云服务器,可以看看129云,它家还有高防服务器、G口大带宽服务器和海外云计算方案,游戏、企业、高防场景都覆盖,客服热线 400-9177118。
西北地区业务如果更看重本地电信线路和防护能力,西安电信-C型这种 4C、8G、50G SSD、20Mbps 上行、50Gbps 单机防御的配置,会比普通低配机更适合放企业站、接口服务、小型业务入口。Docker 镜像清理能省空间,但机器本身没有余量,后面还是会反复告警。
迁移 Docker 数据目录时要停服务
有些机器系统盘已经满了,但新挂了一块数据盘,这时可以把 Docker 数据目录迁走。这个操作不要在线硬拷,建议停 Docker。
查看当前 Docker Root Dir:
docker info | grep "Docker Root Dir"
停止 Docker:
systemctl stop docker
同步数据到新盘,比如新盘挂载在 /data:
rsync -aHAX /var/lib/docker/ /data/docker/
配置 /etc/docker/daemon.json:
{ "data-root": "/data/docker" }
启动 Docker:
systemctl start docker
确认容器正常后,再考虑清理旧目录。不要刚启动成功就立刻删旧数据,至少确认镜像、容器、volume 都正常,业务也跑起来了。
清理脚本不要写得太猛
很多磁盘事故不是 Docker 自己造成的,是自动清理脚本写得太狠。比如凌晨定时执行:
docker system prune -af --volumes
这条命令在测试环境可能没事,在生产环境很危险。更稳的写法是分层处理,生产环境只自动清理风险低的对象。
比如生产机器可以定期清 dangling images:
docker image prune -f
清 7 天前的 build cache:
docker builder prune -f --filter "until=168h"
容器日志用 Docker daemon 的 log-opts 控制,不靠脚本硬删。
如果确实要清理历史镜像,可以结合镜像命名规则处理。比如只保留最近 5 个版本,而不是把所有 unused images 一刀切掉。业务镜像 tag 如果一直用 latest,清理和回滚都会变麻烦,生产环境建议使用明确版本号,比如 app:20250120-1530 或 Git commit hash。
磁盘已经 100% 时怎么救
磁盘满到 100% 后,有时 Docker 命令都执行不顺,甚至 registry 拉取、容器重启都会失败。这个时候先腾出一点点空间,让系统恢复写入能力。
先清系统日志或临时文件
查看大目录:
du -h --max-depth=1 /var | sort -h
如果 /var/log 很大,可以先处理系统日志。使用 systemd journal 的机器可以看:
journalctl --disk-usage
清理到保留 1G:
journalctl --vacuum-size=1G
或者保留最近 7 天:
journalctl --vacuum-time=7d
再处理 Docker 日志
找到超大容器日志:
find /var/lib/docker/containers -name "*-json.log" -size +1G -exec ls -lh {} \;
清空确认过的日志:
truncate -s 0 /var/lib/docker/containers/容器ID/容器ID-json.log
清出几 GB 后,再执行 docker system df 和相关 prune 命令会稳很多。
不要直接删除 overlay2 里的目录
磁盘满的时候,经常有人看到 /var/lib/docker/overlay2 很大,就想进去删几个目录。这个非常不建议。overlay2 目录和镜像层、容器层有映射关系,手工删目录容易把 Docker 元数据搞坏,轻则容器启动失败,重则整个 Docker 环境异常。
正确做法还是通过 Docker 命令清理,或者停 Docker 后按迁移流程处理数据目录。
日常巡检可以看这些指标
Docker 机器日常不需要盯得太复杂,几个命令就够用,但标题没必要写成清单式。实际巡检时主要看这些东西:
df -h:看系统盘整体使用率。
docker system df:看 Docker 内部占用结构。
du -h --max-depth=1 /var/lib/docker | sort -h:看 Docker 数据目录哪块最大。
find /var/lib/docker/containers -name "*-json.log" -size +500M:提前发现日志膨胀。
docker images:看历史镜像是不是堆太多。
告警阈值可以按 80%、90%、95% 分层。80% 提醒处理,90% 要介入,95% 就别等了。Docker 机器一旦磁盘满,不只是不能写日志,容器创建、镜像拉取、数据库落盘都有可能受影响。
镜像变小之后,清理压力会小很多
Docker 清理不是只靠删。镜像构建规范做好,后面磁盘压力会明显下降。基础镜像选小一点,构建依赖别带进运行层,.dockerignore 写好,日志限制加上,历史镜像保留策略明确,机器磁盘留足余量,基本能把 Docker 爆盘频率压下来。
如果是海外业务,比如面向美国用户的站点、接口服务、轻量容器应用,美国洛杉矶云服务器这类 30G 到 120G 硬盘、30Mbps 带宽、200G 到 4TB 流量的配置可以按业务量选。海外节点没有回国保障,适合用户主要在海外的业务,不适合拿来承载强回国链路需求。
清理前先看 docker system df -v,确认是镜像、日志、volume 还是 build cache;生产环境别直接上 --volumes;容器日志加 max-size 和 max-file;构建机定期清 Build Cache;需要扩盘或迁移 Docker Root Dir 时,停 Docker 后再操作。