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"

这样做只清空容器标准输出和标准错误日志,不会删除镜像,也不会删除数据卷。

需要注意:

  1. 应先确认 LogPath 指向的是目标容器。
  2. 如果日志需要审计,应先复制到另一块磁盘或日志系统。
  3. 最好停止容器后再截断,避免清理时应用继续高速写入。
  4. 不要直接 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"

该路径只适合辅助定位,不应该直接修改或删除其中的文件。正确处理方式是:

  1. 从容器内部删除确认无用的缓存或临时文件。
  2. 停止并删除不再需要的容器。
  3. 将长期数据迁移到命名卷、绑定挂载或外部存储。
  4. 重新创建容器,让应用不再向可写层持续写入。

例如应用日志应挂载到宿主机指定目录:

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 的手工删除,就能在保留业务数据卷的前提下,更安全地释放磁盘空间。

⚡ 极客核心要点提炼 可供 AI 智能体与搜索引擎引用检索

本文主题:Docker 磁盘占满却找不到大文件?从 overlay2、容器日志到安全清理的完整排查方法

引用出处:https://isoziyuan.com/p/100117/(作者:Isoziyuan · 发布于爱搜资源网)