> ## Content Index
> Fetch the complete content index at: https://isoziyuan.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Docker 磁盘占满却找不到大文件？从 overlay2、容器日志到安全清理的完整排查方法
- URL: https://isoziyuan.com/p/100117/
- Published: 2026-08-30T07:14:56.000Z
- Updated: 2026-08-30T07:14:56.000Z
- Author: Isoziyuan
- Tags: Docker, 故障排查, 服务器运维, 磁盘清理

## 先判断：真的是 Docker 占满了吗

服务器出现以下现象时，不能直接认定是 `overlay2` 导致的：

- `df -h` 显示磁盘使用率接近 100%
- `du` 却找不到对应大小的文件
- `/var/lib/docker/overlay2` 很大，但不知道哪些目录可以删除
- 删除了日志文件，磁盘空间仍未释放
- Docker 创建容器、拉取镜像或写日志时报 `no space left on device`

首先确认 Docker 实际使用的数据目录和存储驱动。以下命令适用于 Linux 上直接运行的 Docker Engine；Docker Desktop 的数据通常位于虚拟机磁盘中，不能完全照搬宿主机路径。

```bash
docker info --format 'Docker Root Dir: {{.DockerRootDir}}'
docker info --format 'Storage Driver: {{.Driver}}'

```

常见结果为：

```text
Docker Root Dir: /var/lib/docker
Storage Driver: overlay2

```

如果配置过 `data-root`、使用 rootless Docker，或者运行环境使用 containerd snapshotter，实际路径可能不是 `/var/lib/docker`。后续操作应以 `Docker Root Dir` 的结果为准。

设置一个便于后续使用的变量：

```bash
DOCKER_ROOT=$(docker info --format '{{.DockerRootDir}}')
echo "$DOCKER_ROOT"

```

检查该目录所在文件系统的容量和 inode：

```bash
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 提供的统计更适合判断空间属于镜像、容器、数据卷还是构建缓存。

```bash
docker system df
docker system df -v

```

重点查看以下类别：

| 类别            | 主要内容                 | 是否可能安全清理            |
| ------------- | -------------------- | ------------------- |
| Images        | 镜像层                  | 未被容器使用的镜像可以清理       |
| Containers    | 容器可写层                | 删除容器会丢失其可写层数据       |
| Local Volumes | 命名卷和匿名卷              | 可能包含数据库等重要数据，不要贸然删除 |
| Build Cache   | Docker/BuildKit 构建缓存 | 通常可以按需清理            |

然后查看 Docker 根目录下各部分的物理占用：

```bash
sudo du -xhd1 "$DOCKER_ROOT" 2>/dev/null | sort -h

```

常见的大目录及其含义如下：

```text
/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。

### 找出容器使用的日志驱动和日志文件

```bash
docker ps -aq | xargs -r docker inspect \
  --format '{{.Name}}{{"\t"}}{{.HostConfig.LogConfig.Type}}{{"\t"}}{{.LogPath}}'

```

也可以直接搜索最大的 JSON 日志：

```bash
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

```

可能得到：

```text
48G  /var/lib/docker/containers/.../...-json.log
7.2G /var/lib/docker/containers/.../...-json.log

```

根据容器 ID 找到容器名称：

```bash
docker ps -a --no-trunc

```

或者直接查看某个容器的日志路径：

```bash
CID=my-container
docker inspect --format '{{.LogPath}}' "$CID"

```

### 如何立即释放超大 JSON 日志

Docker 没有通用的“清空当前日志”命令。紧急处理时，建议先停止对应容器，再截断其日志文件：

```bash
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"

```

清理后确认空间：

```bash
df -hT "$DOCKER_ROOT"
sudo stat "$LOG_FILE"

```

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

需要注意：

1. 应先确认 `LogPath` 指向的是目标容器。
2. 如果日志需要审计，应先复制到另一块磁盘或日志系统。
3. 最好停止容器后再截断，避免清理时应用继续高速写入。
4. 不要直接 `rm` 正在使用的日志文件。删除文件名不等于关闭进程持有的文件描述符，磁盘空间可能仍不释放。

如果业务不能停止，可以在明确风险后对原文件执行 `truncate`，但这属于应急处理；长期方案仍然是设置日志轮转并重新创建容器。

### 如果使用的是 journald

查看容器日志驱动：

```bash
docker inspect --format '{{.HostConfig.LogConfig.Type}}' my-container

```

如果结果为 `journald`，日志空间由 systemd journal 管理，而不是某个 `*-json.log` 文件。

查看占用：

```bash
sudo journalctl --disk-usage

```

按容量收缩日志：

```bash
sudo journalctl --vacuum-size=1G

```

或者只保留最近 7 天：

```bash
sudo journalctl --vacuum-time=7d

```

这些命令会影响整个 systemd journal，不只影响 Docker 容器日志，执行前应确认系统的日志保留要求。

---

## 第二类高频原因：容器可写层把 overlay2 撑大

`overlay2` 不只是“缓存目录”，它同时存放：

- 镜像只读层
- 容器可写层
- 层之间的引用和元数据
- 运行中容器的 overlay 挂载目录

因此，不能根据某个随机目录的大小直接执行 `rm -rf`。

### 查看各容器的可写层大小

```bash
docker ps -a --size

```

更详细的信息：

```bash
docker system df -v

```

如果某个容器的 `SIZE` 很大，通常说明应用正在把数据写入容器根文件系统，而不是数据卷，例如：

- 日志写入 `/app/logs`
- 上传文件写入 `/app/uploads`
- 临时文件堆积在 `/tmp`
- 包管理器缓存没有清理
- 数据库文件没有挂载到数据卷
- 应用不断生成缓存、转储或 core 文件

检查容器内一级目录占用：

```bash
CID=my-container

docker exec "$CID" sh -c \
  'du -x -h --max-depth=1 / 2>/dev/null | sort -h'

```

部分精简镜像中的 `du` 不支持 `--max-depth`，可以进入容器后按具体目录检查：

```bash
docker exec -it "$CID" sh
du -sh /var/* /app/* /tmp/* 2>/dev/null

```

还可以查看容器相对于镜像发生了哪些文件变化：

```bash
docker diff "$CID"

```

输出标识：

- `A`：新增文件或目录
- `C`：发生修改
- `D`：被删除

例如发现 `/app/logs`、`/tmp/cache` 下大量新增内容，就可以继续从容器内部处理。

### 查看容器对应的 UpperDir

在使用经典 `overlay2` 存储驱动时，可以查看容器可写层路径：

```bash
docker inspect --format '{{.GraphDriver.Data.UpperDir}}' "$CID"

```

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

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

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

```yaml
services:
  app:
    image: example/app:latest
    volumes:
      - /srv/app-logs:/app/logs

```

数据库数据更适合使用命名卷：

```yaml
services:
  db:
    image: postgres:17
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

```

迁移前必须确认镜像内实际的数据目录，不同软件和镜像版本可能不同。

---

## 第三类原因：镜像和构建缓存长期累积

频繁执行以下操作时，Docker 主机很容易积累大量无用层：

- CI/CD 不断构建新镜像
- 镜像标签反复覆盖
- 多阶段构建产生缓存
- 长期拉取新版本但不清理旧版本
- Buildx 创建了独立的构建器缓存

### 先查看镜像和构建缓存

```bash
docker image ls
docker image ls --filter dangling=true
docker builder du

```

如果使用 Buildx：

```bash
docker buildx ls
docker buildx du

```

### 分级清理，而不是一次性全删

只删除悬空镜像：

```bash
docker image prune

```

删除未被任何容器引用的镜像：

```bash
docker image prune -a

```

`-a` 的范围更大。虽然不会删除正在被容器引用的镜像，但可能删除后续部署还要使用的本地镜像，之后需要重新拉取。

清理普通构建缓存：

```bash
docker builder prune

```

删除所有未使用的构建缓存：

```bash
docker builder prune -a

```

清理 Buildx 缓存：

```bash
docker buildx prune

```

空间极度紧张时，也可以设置保留上限：

```bash
docker builder prune --keep-storage 10GB

```

执行前应阅读 Docker 输出的待清理对象和预计可释放空间，再确认操作。

---

## 第四类原因：停止的容器仍保留了巨大可写层

停止容器并不会删除容器可写层。先检查：

```bash
docker ps -a --size

```

删除一个确认无用的停止容器：

```bash
docker rm container_name

```

批量清理所有停止容器：

```bash
docker container prune

```

这里有一个容易忽略的风险：删除容器虽然不会自动删除命名数据卷，但会删除该容器自身的可写层。如果有人把数据库、上传文件或业务数据错误地写在容器层中，这些内容会随容器删除。

因此，在执行 `docker container prune` 前至少确认：

```bash
docker ps -a --size
docker inspect container_name --format '{{json .Mounts}}'
docker diff container_name

```

不要把“数据卷不会删除”误解为“容器中的所有数据都不会删除”。

---

## 不删除数据卷时，推荐的清理顺序

如果目标是释放 Docker 空间，同时明确要求保留数据卷，可以按以下顺序处理。

### 1\. 先停止异常写入

找出持续产生日志或文件的容器：

```bash
docker stats
docker ps --size

```

必要时先停止异常容器，避免清理速度赶不上写入速度：

```bash
docker stop container_name

```

### 2\. 清理超大容器日志

确认日志驱动和路径后，停止容器并截断对应日志文件：

```bash
CID=container_name
LOG_FILE=$(docker inspect --format '{{.LogPath}}' "$CID")

docker stop "$CID"
sudo truncate -s 0 "$LOG_FILE"
docker start "$CID"

```

### 3\. 清理构建缓存

```bash
docker builder prune

```

使用 Buildx 时：

```bash
docker buildx prune

```

### 4\. 清理悬空镜像

```bash
docker image prune

```

### 5\. 审核后删除停止容器

```bash
docker ps -a --size
docker rm confirmed_unused_container

```

### 6\. 审核后清理更多未使用镜像

```bash
docker image prune -a

```

### 7\. 重新检查空间

```bash
docker system df -v
df -hT "$DOCKER_ROOT"
df -ih "$DOCKER_ROOT"

```

整个过程中不要执行：

```bash
docker volume prune

```

也不要给系统清理命令增加：

```bash
--volumes

```

更不要手动删除：

```bash
rm -rf /var/lib/docker/volumes/*
rm -rf /var/lib/docker/overlay2/*

```

---

## `docker system prune` 会不会删除数据卷

基础命令：

```bash
docker system prune

```

通常会清理：

- 已停止的容器
- 未使用的网络
- 悬空镜像
- 未使用的构建缓存

它不会因为默认执行就清理所有数据卷，但仍可能删除停止容器的可写层。因此，运行前应确认停止容器中没有未迁移的数据。

更彻底的命令：

```bash
docker system prune -a

```

还会清理未被任何容器引用的镜像。

如果要求不删除数据卷，就不要使用：

```bash
docker system prune --volumes
docker volume prune

```

相较于直接执行一次大范围 `system prune`，逐类使用 `container prune`、`image prune` 和 `builder prune` 更容易确认影响范围。

---

## 为什么删除了大文件，`df` 还是没有下降

Linux 文件被删除后，如果仍被某个进程打开，其磁盘块不会立即释放。此时：

- `du` 已经找不到文件
- `df` 仍显示磁盘已满
- `lsof` 会显示文件状态为 `(deleted)`

检查已删除但仍被占用的文件：

```bash
sudo lsof +L1

```

只关注 Docker 相关路径：

```bash
sudo lsof +L1 | grep -E '/var/lib/docker|/var/log'

```

如果之前直接 `rm` 了正在写入的 Docker JSON 日志，常见结果类似：

```text
dockerd  1234 root  ... /var/lib/docker/containers/...-json.log (deleted)

```

解决方法是让持有该文件的进程关闭文件描述符。优先重启对应容器：

```bash
docker restart container_name

```

如果无法确定具体容器，可能需要在维护窗口重启 Docker：

```bash
sudo systemctl restart docker

```

重启 Docker 可能影响所有容器，是否自动恢复取决于容器重启策略和 Docker 配置，执行前应检查：

```bash
docker ps --format 'table {{.Names}}\t{{.Status}}'
docker inspect --format '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)

```

不要为了释放空间直接杀死未知进程，先确认其所属服务。

---

## 为什么 `du` 和 `df` 的结果对不上

常见原因有四类。

### 已删除文件仍被进程占用

使用：

```bash
sudo lsof +L1

```

### 文件系统保留块

ext4 通常会为特权进程保留一部分空间。查看文件系统信息时，可使用：

```bash
sudo tune2fs -l /dev/实际设备 | grep -E 'Reserved block count|Block count|Block size'

```

不要在不理解影响的情况下随意修改保留比例，尤其是系统根分区。

### inode 被大量小文件耗尽

检查：

```bash
df -i

```

如果 inode 已满，应寻找小文件密集目录，而不是只查找大文件：

```bash
sudo du --inodes -x -d 2 "$DOCKER_ROOT" 2>/dev/null | sort -n | tail -30

```

### overlay 挂载和共享层导致统计口径不同

Docker 镜像层可以被多个镜像和容器共享。`docker system df` 展示的是 Docker 对象及其可回收关系，而 `du` 更接近文件系统实际占用，两者不一定完全相等。

判断“能释放多少”时，应优先参考：

```bash
docker system df -v

```

判断“磁盘到底被哪些目录占用”时，再结合：

```bash
sudo du -xhd1 "$DOCKER_ROOT"
df -hT "$DOCKER_ROOT"

```

---

## 给容器日志设置上限，避免再次占满

一次性截断日志只能救急，必须配置日志轮转。

### 方案一：使用 local 日志驱动

Docker 的 `local` 日志驱动针对本地存储进行了优化，并支持轮转。可以在 `/etc/docker/daemon.json` 中配置：

```json
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  }
}

```

如果该文件已经包含镜像加速、私有仓库、`data-root` 等配置，必须合并 JSON 字段，不能直接覆盖原文件。

部分 Docker 版本可以在重启前验证配置：

```bash
sudo dockerd --validate --config-file=/etc/docker/daemon.json

```

然后在维护窗口重启 Docker：

```bash
sudo systemctl restart docker

```

### 方案二：继续使用 json-file，但限制大小

```json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  }
}

```

该配置表示单个日志文件最大约 20 MB，最多保留 5 个轮转文件。实际总占用还要考虑容器数量以及当前活动日志文件。

### Docker Compose 中按服务配置

```yaml
services:
  app:
    image: example/app:latest
    logging:
      driver: local
      options:
        max-size: "20m"
        max-file: "5"

```

或者使用 `json-file`：

```yaml
services:
  app:
    image: example/app:latest
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

```

一个关键点是：修改 Docker 守护进程的默认日志配置，不会自动改变已有容器的日志配置。需要重新创建容器才能生效。

Compose 项目可以在确认配置和数据挂载后执行：

```bash
docker compose up -d --force-recreate

```

该操作通常不会删除命名卷，但重新创建容器会丢弃旧容器可写层中的内容，因此仍需先确认业务数据已经正确挂载。

验证新容器的日志配置：

```bash
docker inspect --format '{{json .HostConfig.LogConfig}}' container_name

```

---

## 绝对不要直接删除 overlay2 目录

以下操作虽然可能让磁盘占用瞬间下降，但很可能直接破坏 Docker 的层关系和元数据：

```bash
sudo rm -rf /var/lib/docker/overlay2/*

```

可能造成：

- 容器无法启动
- 镜像层损坏
- `docker inspect` 与实际文件不一致
- 数据无法通过 Docker 正常恢复
- Docker 守护进程持续报错
- 运行中容器异常退出或文件系统损坏

同样，不要手动删除这些目录中的随机内容：

```text
/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\. 停止日志量最大的容器

```bash
docker ps
docker stop suspected_container

```

### 2\. 截断已确认的超大 JSON 日志

先获取路径，再操作：

```bash
LOG_FILE=$(docker inspect --format '{{.LogPath}}' suspected_container)
sudo ls -lh "$LOG_FILE"
sudo truncate -s 0 "$LOG_FILE"

```

不要通过模糊通配符一次清空所有文件。

### 3\. 清理系统自身的安全缓存

例如确认无用的包缓存：

```bash
sudo apt-get clean

```

或者在使用 DNF 的系统上：

```bash
sudo dnf clean all

```

这一步只是为了腾出少量工作空间，让 Docker 能正常执行后续清理。

### 4\. 再使用 Docker 命令清理对象

```bash
docker builder prune
docker image prune

```

如果 Docker 守护进程已经异常，应先查看状态：

```bash
sudo systemctl status docker
sudo journalctl -u docker --since '30 minutes ago'

```

不建议在空间耗尽时直接删除 Docker 内部目录“抢救”，因为这可能把容量问题升级成数据损坏问题。

---

## 一套可直接执行的排查清单

先确认环境：

```bash
DOCKER_ROOT=$(docker info --format '{{.DockerRootDir}}')

docker info --format 'Root={{.DockerRootDir}} Driver={{.Driver}}'
df -hT "$DOCKER_ROOT"
df -ih "$DOCKER_ROOT"

```

查看 Docker 对象占用：

```bash
docker system df
docker system df -v
docker ps -a --size

```

查看 Docker 根目录：

```bash
sudo du -xhd1 "$DOCKER_ROOT" 2>/dev/null | sort -h

```

查找超大 JSON 日志：

```bash
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

```

检查已删除但未释放的文件：

```bash
sudo lsof +L1 | grep -E '/var/lib/docker|/var/log'

```

检查构建缓存：

```bash
docker builder du
docker buildx du 2>/dev/null

```

按风险从低到高进行清理：

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