Docker 磁盘占满却找不到大文件?从 overlay2、容器日志到安全清理的完整排查方法
先判断:真的是 Docker 占满了吗
服务器出现以下现象时,不能直接认定是 overlay2 导致的:
df -h显示磁盘使用率接近 100%du却找不到对应大小的文件/var/lib/docker/overlay2很大,但不知道哪些目录可以删除- 删除了日志文件,磁盘空间仍未释放
- Docker 创建容器、拉取镜像或写日志时报
no space left on device
首先确认 Docker 实际使用的数据目录和存储驱动。以下命令适用于 Linux 上直接运行的 Docker Engine;Docker Desktop 的数据通常位于虚拟机磁盘中,不能完全照搬宿主机路径。
docker info --format 'Docker Root Dir: {{.DockerRootDir}}'
docker info --format 'Storage Driver: {{.Driver}}'
常见结果为:
Docker Root Dir: /var/lib/docker
Storage Driver: overlay2
如果配置过 data-root、使用 rootless Docker,或者运行环境使用 containerd snapshotter,实际路径可能不是 /var/lib/docker。后续操作应以 Docker Root Dir 的结果为准。
设置一个便于后续使用的变量:
DOCKER_ROOT=$(docker info --format '{{.DockerRootDir}}')
echo "$DOCKER_ROOT"
检查该目录所在文件系统的容量和 inode:
df -hT "$DOCKER_ROOT"
df -ih "$DOCKER_ROOT"
findmnt -T "$DOCKER_ROOT"
这里需要区分两种“满”:
Use%接近 100%:磁盘块空间耗尽。IUse%接近 100%:inode 耗尽,通常是大量小文件造成的。
inode 用完时,即使磁盘还有很多 GB 空间,也可能出现 no space left on device。
用 Docker 自己的统计先确定清理方向
先不要进入 overlay2 目录逐个删除。Docker 提供的统计更适合判断空间属于镜像、容器、数据卷还是构建缓存。
docker system df
docker system df -v
重点查看以下类别:
| 类别 | 主要内容 | 是否可能安全清理 |
|---|---|---|
| Images | 镜像层 | 未被容器使用的镜像可以清理 |
| Containers | 容器可写层 | 删除容器会丢失其可写层数据 |
| Local Volumes | 命名卷和匿名卷 | 可能包含数据库等重要数据,不要贸然删除 |
| Build Cache | Docker/BuildKit 构建缓存 | 通常可以按需清理 |
然后查看 Docker 根目录下各部分的物理占用:
sudo du -xhd1 "$DOCKER_ROOT" 2>/dev/null | sort -h
常见的大目录及其含义如下:
/var/lib/docker/containers 容器元数据及部分日志
/var/lib/docker/overlay2 镜像层和容器可写层
/var/lib/docker/volumes Docker 数据卷
/var/lib/docker/buildkit BuildKit 构建缓存
/var/lib/docker/image 镜像元数据
du 使用 -x 是为了避免递归进入其他文件系统或 overlay 挂载点,减少重复统计和结果失真。
第一类高频原因:容器日志无限增长
Docker 默认常见的日志驱动是 json-file。如果没有设置轮转限制,容器持续输出日志时,单个日志文件可能增长到几十 GB。
找出容器使用的日志驱动和日志文件
docker ps -aq | xargs -r docker inspect \
--format '{{.Name}}{{"\t"}}{{.HostConfig.LogConfig.Type}}{{"\t"}}{{.LogPath}}'
也可以直接搜索最大的 JSON 日志:
sudo find "$DOCKER_ROOT/containers" \
-type f -name '*-json.log' \
-printf '%s\t%p\n' 2>/dev/null \
| sort -nr \
| head -20 \
| numfmt --field=1 --to=iec
可能得到:
48G /var/lib/docker/containers/.../...-json.log
7.2G /var/lib/docker/containers/.../...-json.log
根据容器 ID 找到容器名称:
docker ps -a --no-trunc
或者直接查看某个容器的日志路径:
CID=my-container
docker inspect --format '{{.LogPath}}' "$CID"
如何立即释放超大 JSON 日志
Docker 没有通用的“清空当前日志”命令。紧急处理时,建议先停止对应容器,再截断其日志文件:
CID=my-container
LOG_FILE=$(docker inspect --format '{{.LogPath}}' "$CID")
printf '日志文件:%s\n' "$LOG_FILE"
sudo stat "$LOG_FILE"
docker stop "$CID"
sudo truncate -s 0 "$LOG_FILE"
docker start "$CID"
清理后确认空间:
df -hT "$DOCKER_ROOT"
sudo stat "$LOG_FILE"
这样做只清空容器标准输出和标准错误日志,不会删除镜像,也不会删除数据卷。
需要注意:
- 应先确认
LogPath指向的是目标容器。 - 如果日志需要审计,应先复制到另一块磁盘或日志系统。
- 最好停止容器后再截断,避免清理时应用继续高速写入。
- 不要直接
rm正在使用的日志文件。删除文件名不等于关闭进程持有的文件描述符,磁盘空间可能仍不释放。
如果业务不能停止,可以在明确风险后对原文件执行 truncate,但这属于应急处理;长期方案仍然是设置日志轮转并重新创建容器。
如果使用的是 journald
查看容器日志驱动:
docker inspect --format '{{.HostConfig.LogConfig.Type}}' my-container
如果结果为 journald,日志空间由 systemd journal 管理,而不是某个 *-json.log 文件。
查看占用:
sudo journalctl --disk-usage
按容量收缩日志:
sudo journalctl --vacuum-size=1G
或者只保留最近 7 天:
sudo journalctl --vacuum-time=7d
这些命令会影响整个 systemd journal,不只影响 Docker 容器日志,执行前应确认系统的日志保留要求。
第二类高频原因:容器可写层把 overlay2 撑大
overlay2 不只是“缓存目录”,它同时存放:
- 镜像只读层
- 容器可写层
- 层之间的引用和元数据
- 运行中容器的 overlay 挂载目录
因此,不能根据某个随机目录的大小直接执行 rm -rf。
查看各容器的可写层大小
docker ps -a --size
更详细的信息:
docker system df -v
如果某个容器的 SIZE 很大,通常说明应用正在把数据写入容器根文件系统,而不是数据卷,例如:
- 日志写入
/app/logs - 上传文件写入
/app/uploads - 临时文件堆积在
/tmp - 包管理器缓存没有清理
- 数据库文件没有挂载到数据卷
- 应用不断生成缓存、转储或 core 文件
检查容器内一级目录占用:
CID=my-container
docker exec "$CID" sh -c \
'du -x -h --max-depth=1 / 2>/dev/null | sort -h'
部分精简镜像中的 du 不支持 --max-depth,可以进入容器后按具体目录检查:
docker exec -it "$CID" sh
du -sh /var/* /app/* /tmp/* 2>/dev/null
还可以查看容器相对于镜像发生了哪些文件变化:
docker diff "$CID"
输出标识:
A:新增文件或目录C:发生修改D:被删除
例如发现 /app/logs、/tmp/cache 下大量新增内容,就可以继续从容器内部处理。
查看容器对应的 UpperDir
在使用经典 overlay2 存储驱动时,可以查看容器可写层路径:
docker inspect --format '{{.GraphDriver.Data.UpperDir}}' "$CID"
该路径只适合辅助定位,不应该直接修改或删除其中的文件。正确处理方式是:
- 从容器内部删除确认无用的缓存或临时文件。
- 停止并删除不再需要的容器。
- 将长期数据迁移到命名卷、绑定挂载或外部存储。
- 重新创建容器,让应用不再向可写层持续写入。
例如应用日志应挂载到宿主机指定目录:
services:
app:
image: example/app:latest
volumes:
- /srv/app-logs:/app/logs
数据库数据更适合使用命名卷:
services:
db:
image: postgres:17
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
迁移前必须确认镜像内实际的数据目录,不同软件和镜像版本可能不同。
第三类原因:镜像和构建缓存长期累积
频繁执行以下操作时,Docker 主机很容易积累大量无用层:
- CI/CD 不断构建新镜像
- 镜像标签反复覆盖
- 多阶段构建产生缓存
- 长期拉取新版本但不清理旧版本
- Buildx 创建了独立的构建器缓存
先查看镜像和构建缓存
docker image ls
docker image ls --filter dangling=true
docker builder du
如果使用 Buildx:
docker buildx ls
docker buildx du
分级清理,而不是一次性全删
只删除悬空镜像:
docker image prune
删除未被任何容器引用的镜像:
docker image prune -a
-a 的范围更大。虽然不会删除正在被容器引用的镜像,但可能删除后续部署还要使用的本地镜像,之后需要重新拉取。
清理普通构建缓存:
docker builder prune
删除所有未使用的构建缓存:
docker builder prune -a
清理 Buildx 缓存:
docker buildx prune
空间极度紧张时,也可以设置保留上限:
docker builder prune --keep-storage 10GB
执行前应阅读 Docker 输出的待清理对象和预计可释放空间,再确认操作。
第四类原因:停止的容器仍保留了巨大可写层
停止容器并不会删除容器可写层。先检查:
docker ps -a --size
删除一个确认无用的停止容器:
docker rm container_name
批量清理所有停止容器:
docker container prune
这里有一个容易忽略的风险:删除容器虽然不会自动删除命名数据卷,但会删除该容器自身的可写层。如果有人把数据库、上传文件或业务数据错误地写在容器层中,这些内容会随容器删除。
因此,在执行 docker container prune 前至少确认:
docker ps -a --size
docker inspect container_name --format '{{json .Mounts}}'
docker diff container_name
不要把“数据卷不会删除”误解为“容器中的所有数据都不会删除”。
不删除数据卷时,推荐的清理顺序
如果目标是释放 Docker 空间,同时明确要求保留数据卷,可以按以下顺序处理。
1. 先停止异常写入
找出持续产生日志或文件的容器:
docker stats
docker ps --size
必要时先停止异常容器,避免清理速度赶不上写入速度:
docker stop container_name
2. 清理超大容器日志
确认日志驱动和路径后,停止容器并截断对应日志文件:
CID=container_name
LOG_FILE=$(docker inspect --format '{{.LogPath}}' "$CID")
docker stop "$CID"
sudo truncate -s 0 "$LOG_FILE"
docker start "$CID"
3. 清理构建缓存
docker builder prune
使用 Buildx 时:
docker buildx prune
4. 清理悬空镜像
docker image prune
5. 审核后删除停止容器
docker ps -a --size
docker rm confirmed_unused_container
6. 审核后清理更多未使用镜像
docker image prune -a
7. 重新检查空间
docker system df -v
df -hT "$DOCKER_ROOT"
df -ih "$DOCKER_ROOT"
整个过程中不要执行:
docker volume prune
也不要给系统清理命令增加:
--volumes
更不要手动删除:
rm -rf /var/lib/docker/volumes/*
rm -rf /var/lib/docker/overlay2/*
docker system prune 会不会删除数据卷
基础命令:
docker system prune
通常会清理:
- 已停止的容器
- 未使用的网络
- 悬空镜像
- 未使用的构建缓存
它不会因为默认执行就清理所有数据卷,但仍可能删除停止容器的可写层。因此,运行前应确认停止容器中没有未迁移的数据。
更彻底的命令:
docker system prune -a
还会清理未被任何容器引用的镜像。
如果要求不删除数据卷,就不要使用:
docker system prune --volumes
docker volume prune
相较于直接执行一次大范围 system prune,逐类使用 container prune、image prune 和 builder prune 更容易确认影响范围。
为什么删除了大文件,df 还是没有下降
Linux 文件被删除后,如果仍被某个进程打开,其磁盘块不会立即释放。此时:
du已经找不到文件df仍显示磁盘已满lsof会显示文件状态为(deleted)
检查已删除但仍被占用的文件:
sudo lsof +L1
只关注 Docker 相关路径:
sudo lsof +L1 | grep -E '/var/lib/docker|/var/log'
如果之前直接 rm 了正在写入的 Docker JSON 日志,常见结果类似:
dockerd 1234 root ... /var/lib/docker/containers/...-json.log (deleted)
解决方法是让持有该文件的进程关闭文件描述符。优先重启对应容器:
docker restart container_name
如果无法确定具体容器,可能需要在维护窗口重启 Docker:
sudo systemctl restart docker
重启 Docker 可能影响所有容器,是否自动恢复取决于容器重启策略和 Docker 配置,执行前应检查:
docker ps --format 'table {{.Names}}\t{{.Status}}'
docker inspect --format '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)
不要为了释放空间直接杀死未知进程,先确认其所属服务。
为什么 du 和 df 的结果对不上
常见原因有四类。
已删除文件仍被进程占用
使用:
sudo lsof +L1
文件系统保留块
ext4 通常会为特权进程保留一部分空间。查看文件系统信息时,可使用:
sudo tune2fs -l /dev/实际设备 | grep -E 'Reserved block count|Block count|Block size'
不要在不理解影响的情况下随意修改保留比例,尤其是系统根分区。
inode 被大量小文件耗尽
检查:
df -i
如果 inode 已满,应寻找小文件密集目录,而不是只查找大文件:
sudo du --inodes -x -d 2 "$DOCKER_ROOT" 2>/dev/null | sort -n | tail -30
overlay 挂载和共享层导致统计口径不同
Docker 镜像层可以被多个镜像和容器共享。docker system df 展示的是 Docker 对象及其可回收关系,而 du 更接近文件系统实际占用,两者不一定完全相等。
判断“能释放多少”时,应优先参考:
docker system df -v
判断“磁盘到底被哪些目录占用”时,再结合:
sudo du -xhd1 "$DOCKER_ROOT"
df -hT "$DOCKER_ROOT"
给容器日志设置上限,避免再次占满
一次性截断日志只能救急,必须配置日志轮转。
方案一:使用 local 日志驱动
Docker 的 local 日志驱动针对本地存储进行了优化,并支持轮转。可以在 /etc/docker/daemon.json 中配置:
{
"log-driver": "local",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
如果该文件已经包含镜像加速、私有仓库、data-root 等配置,必须合并 JSON 字段,不能直接覆盖原文件。
部分 Docker 版本可以在重启前验证配置:
sudo dockerd --validate --config-file=/etc/docker/daemon.json
然后在维护窗口重启 Docker:
sudo systemctl restart docker
方案二:继续使用 json-file,但限制大小
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
该配置表示单个日志文件最大约 20 MB,最多保留 5 个轮转文件。实际总占用还要考虑容器数量以及当前活动日志文件。
Docker Compose 中按服务配置
services:
app:
image: example/app:latest
logging:
driver: local
options:
max-size: "20m"
max-file: "5"
或者使用 json-file:
services:
app:
image: example/app:latest
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
一个关键点是:修改 Docker 守护进程的默认日志配置,不会自动改变已有容器的日志配置。需要重新创建容器才能生效。
Compose 项目可以在确认配置和数据挂载后执行:
docker compose up -d --force-recreate
该操作通常不会删除命名卷,但重新创建容器会丢弃旧容器可写层中的内容,因此仍需先确认业务数据已经正确挂载。
验证新容器的日志配置:
docker inspect --format '{{json .HostConfig.LogConfig}}' container_name
绝对不要直接删除 overlay2 目录
以下操作虽然可能让磁盘占用瞬间下降,但很可能直接破坏 Docker 的层关系和元数据:
sudo rm -rf /var/lib/docker/overlay2/*
可能造成:
- 容器无法启动
- 镜像层损坏
docker inspect与实际文件不一致- 数据无法通过 Docker 正常恢复
- Docker 守护进程持续报错
- 运行中容器异常退出或文件系统损坏
同样,不要手动删除这些目录中的随机内容:
/var/lib/docker/image
/var/lib/docker/containers
/var/lib/docker/volumes
/var/lib/docker/buildkit
正确原则是:
- 日志通过日志路径和轮转策略处理。
- 容器通过
docker rm处理。 - 镜像通过
docker image rm/prune处理。 - 构建缓存通过
docker builder prune处理。 - 数据卷只在明确确认无用后,通过 Docker 命令处理。
- 容器可写层中的无用文件,应从容器内部删除或通过重建容器清理。
磁盘已经 100%,Docker 命令也执行失败怎么办
磁盘完全没有可写空间时,Docker 可能无法创建临时文件或更新元数据。可以按以下顺序应急处理。
1. 停止日志量最大的容器
docker ps
docker stop suspected_container
2. 截断已确认的超大 JSON 日志
先获取路径,再操作:
LOG_FILE=$(docker inspect --format '{{.LogPath}}' suspected_container)
sudo ls -lh "$LOG_FILE"
sudo truncate -s 0 "$LOG_FILE"
不要通过模糊通配符一次清空所有文件。
3. 清理系统自身的安全缓存
例如确认无用的包缓存:
sudo apt-get clean
或者在使用 DNF 的系统上:
sudo dnf clean all
这一步只是为了腾出少量工作空间,让 Docker 能正常执行后续清理。
4. 再使用 Docker 命令清理对象
docker builder prune
docker image prune
如果 Docker 守护进程已经异常,应先查看状态:
sudo systemctl status docker
sudo journalctl -u docker --since '30 minutes ago'
不建议在空间耗尽时直接删除 Docker 内部目录“抢救”,因为这可能把容量问题升级成数据损坏问题。
一套可直接执行的排查清单
先确认环境:
DOCKER_ROOT=$(docker info --format '{{.DockerRootDir}}')
docker info --format 'Root={{.DockerRootDir}} Driver={{.Driver}}'
df -hT "$DOCKER_ROOT"
df -ih "$DOCKER_ROOT"
查看 Docker 对象占用:
docker system df
docker system df -v
docker ps -a --size
查看 Docker 根目录:
sudo du -xhd1 "$DOCKER_ROOT" 2>/dev/null | sort -h
查找超大 JSON 日志:
sudo find "$DOCKER_ROOT/containers" \
-type f -name '*-json.log' \
-printf '%s\t%p\n' 2>/dev/null \
| sort -nr \
| head -20 \
| numfmt --field=1 --to=iec
检查已删除但未释放的文件:
sudo lsof +L1 | grep -E '/var/lib/docker|/var/log'
检查构建缓存:
docker builder du
docker buildx du 2>/dev/null
按风险从低到高进行清理:
docker builder prune
docker image prune
docker container prune
docker image prune -a
其中 container prune 和 image prune -a 必须在确认对象无用后执行。为了保留数据卷,不要使用任何带 --volumes 的清理命令。
排查结论如何快速对应处理
| 发现的问题 | 推荐处理 |
|---|---|
containers 目录很大 |
检查 *-json.log 和容器日志驱动 |
overlay2 很大,某个容器 SIZE 很大 |
从容器内部查日志、缓存、上传文件和临时文件 |
overlay2 很大,但容器可写层不大 |
检查旧镜像、共享镜像层和停止容器 |
buildkit 很大 |
使用 docker builder prune |
docker system df 显示 Build Cache 很大 |
清理普通或 Buildx 构建缓存 |
du 很小但 df 很大 |
使用 lsof +L1 查找已删除但仍打开的文件 |
磁盘有空间却报 no space left on device |
检查 df -i,可能是 inode 耗尽 |
volumes 很大 |
先识别卷归属,不要直接删除 |
| 清空日志后很快又满 | 配置日志轮转,并重新创建已有容器 |
| overlay2 中某个随机目录很大 | 不要直接删除,先映射到 Docker 对象或从容器内部处理 |
解决 Docker 磁盘占满问题的核心不是“找到最大的目录就删除”,而是先区分日志、容器可写层、镜像、构建缓存和数据卷。只要坚持通过 Docker 对象关系进行清理,并避开 overlay2 和 volumes 的手工删除,就能在保留业务数据卷的前提下,更安全地释放磁盘空间。
本文主题:Docker 磁盘占满却找不到大文件?从 overlay2、容器日志到安全清理的完整排查方法
引用出处:https://isoziyuan.com/p/100117/(作者:Isoziyuan · 发布于爱搜资源网)
登录后参与讨论
注册或登录账户,即可查看并发表文章评论。