大型 Docker 开发镜像磁盘排查:containerd、缓存与容量规划

摘要:给出大型开发镜像的磁盘排查顺序,区分 containerd 镜像数据、snapshot、BuildKit cache、导出归档和源码构建输出。

在这里插入图片描述

@[toc]

当一个二十多 GiB 的开发镜像运行在 Docker Engine 29+ 新主机上时,磁盘问题往往比容器运行问题更早出现。

例如根分区只有约 80 GiB,却同时存在:

1
2
3
4
5
6
大型开发镜像
containerd content
unpacked snapshot
BuildKit cache
源码 build 输出
镜像导出归档

这时如果只看到:

1
/dev/sdaX 90%

然后立刻运行 docker system prune -a,很容易删掉重新构建代价很高的开发镜像。

更好的方式是先回答:

空间到底被哪一类数据占用了?

1. 第一层先看文件系统

1
df -h /

它回答的是:

1
整个根文件系统还剩多少空间?

它不会告诉你 Docker 哪个对象在占空间。

如果镜像归档放在其他分区,还要分别看:

1
df -h /path/to/archive

大型镜像导出时,Docker 数据和归档文件可能位于不同文件系统,不能只检查一个路径。

2. 第二层看宿主机真实目录

containerd image store 环境下,至少检查:

1
sudo du -sh /var/lib/containerd /var/lib/docker

这回答的是宿主机目录实际占用。

Docker 当前文档说明,containerd image store 下的 image contents 和 container snapshots 默认在:

1
/var/lib/containerd

所以新 Docker 环境中只看 /var/lib/docker 已经不够。

3. 第三层看 Docker 逻辑对象

1
docker system df

需要更详细时:

1
docker system df -v

它让你从 Docker 对象视角看:

1
2
3
4
Images
Containers
Local Volumes
Build Cache

dudocker system df 不要求数字完全一致。

一个是文件系统目录视角,一个是 Docker 对象和共享关系视角。

4. 构建过大镜像以后单独看 BuildKit cache

如果刚执行过大型 docker buildx build,再看:

1
docker buildx du

大型 SDK 构建经常产生很大的 BuildKit cache。

它和“最终镜像本身”不是同一个对象:

1
2
3
4
5
Build Cache
构建阶段复用

Final Image
docker run 使用

如果 cache 明确可回收,可以:

1
docker builder prune

这通常是大型镜像构建完成后的第一类清理对象。

5. 为什么 builder prune 不会把 50 GiB 镜像变成 27 GiB

如果当前 containerd image store 需要同时保存:

1
2
image content
unpacked snapshot

那么它们属于最终镜像运行体系。

执行:

1
docker builder prune

只会处理 BuildKit cache。

所以即使清掉 20 GiB 构建缓存,最终镜像相关的几十 GiB 仍然可能存在。

这不是 prune 失败,而是你清理的是不同对象。

6. inactive 不等于“不需要”

大型开发镜像有一个危险特点:平时不一定有常驻容器。

所以 Docker 可能显示:

1
ACTIVE 0

但这只说明现在没有容器使用它,不代表镜像应该删除。

一个 20+ GiB 开发镜像可能构建几个小时,甚至需要重新复制大型 SDK。

因此看到 inactive 后不要直接:

1
docker system prune -a

更合理的是先确定:

1
2
3
这个镜像是否是正式开发环境?
是否已经有可靠离线备份?
是否能快速重新构建?

7. image prune 和 system prune 的范围不同

较温和:

1
docker image prune

更激进:

1
docker image prune -a

范围更广:

1
docker system prune -a

越往后,自动判断“未使用”的对象范围越大。

对于普通 CI 临时环境,这可能很方便;对于手工制作的大型 SDK 镜像,应该更保守。

建议先:

1
docker system df -v

确认对象,再做针对性清理。

8. 不要直接删除 /var/lib/containerd

即使:

1
sudo du -sh /var/lib/containerd

显示几十 GiB,也不要执行:

1
sudo rm -rf /var/lib/containerd/*

containerd 的 content、metadata 和 snapshot 之间存在引用关系。

绕过 Docker/containerd 管理层手工删除,可能导致:

1
2
3
4
镜像 metadata 仍然存在
底层 blob 已损坏
snapshot 引用不一致
容器无法启动

底层目录不是普通“缓存目录”。

清理应优先通过 Docker CLI 或明确的官方迁移流程完成。

9. 导出归档经常是被忽略的一大块

例如你为了迁移镜像,在:

1
~/images/

放了:

1
2
cross-dev.tar
cross-dev.tar.zst

如果原始 TAR 没删,可能同时多占:

1
2
3
20+ GiB TAR
+
10+ GiB 压缩归档

它们完全不属于 Docker,因此:

1
docker system df

不会显示。

这就是为什么排查不能只看 Docker CLI。

可以检查:

1
2
du -sh ~/images
find ~/images -maxdepth 1 -type f -size +1G -ls

大型镜像场景里,“导出文件忘了删”非常常见。

10. 源码 build 目录也可能很大

如果开发容器使用 bind mount:

1
宿主机源码 -> /workspace

那么容器生成的:

1
2
3
4
5
build/
*.o
*.a
*.so
打包产物

都可能实际写在宿主机项目目录。

这些也不属于 Docker image store。

所以完整排查还应该看:

1
2
du -sh ~/work/demo-app
du -sh ~/work/demo-app/build

不要看到“是容器编译产生的”就认为一定在 /var/lib/docker

11. data-root 迁走后为什么根分区还可能增长

Docker 常见配置:

1
2
3
{
"data-root": "/mnt/docker-data"
}

但 Docker 当前官方文档明确说明:在 containerd image store 下,data-root 不会自动改变 containerd image contents 和 snapshots 的位置。

于是可能变成:

1
2
/mnt/docker-data     Docker 其他数据
/var/lib/containerd 镜像内容和 snapshot

如果你的目标是彻底把大型镜像数据迁出根分区,就必须把 containerd 数据位置也纳入设计,而不是只修改 Docker data-root

这类存储迁移涉及 daemon 停止、数据复制和配置一致性,不建议在磁盘快满时临时手工移动目录;应按 Docker/containerd 官方数据目录配置方式操作。

12. 一个 80 GiB 根分区为什么很容易不够

假设:

1
2
3
4
系统自身              15 GiB
开发镜像相关数据 45~55 GiB
BuildKit cache 10 GiB
源码和 build 5 GiB

总量已经可能达到:

1
75~85 GiB

如果这时还在根分区导出:

1
cross-dev.tar.zst 10+ GiB

很容易直接失败。

因此对超大型开发镜像,磁盘规划应该比“镜像 SIZE + 5 GiB”保守得多。

13. 更实用的容量规划方式

可以按几类数据分别估算:

1
2
3
4
5
6
A. 操作系统和普通工具
B. containerd image content
C. unpacked snapshots
D. BuildKit cache 峰值
E. 源码和构建产物
F. 镜像导出归档

其中 F 最适合放到另一块盘或网络存储,因为它只在迁移时需要。

如果这台机器长期维护大 SDK 镜像,更合理的硬件布局是:

1
2
3
4
5
6
系统根分区
放 OS 和普通工具

大容量数据分区
放 Docker/containerd 数据
或至少放离线镜像归档

14. 推荐的排查顺序

以后遇到 Docker 把磁盘吃满,可以按下面顺序:

1
2
3
4
5
6
flowchart TD
A["df -h:文件系统还剩多少"] --> B["du:/var/lib/containerd 与 /var/lib/docker"]
B --> C["docker system df -v:镜像/容器/volume"]
C --> D["docker buildx du:BuildKit cache"]
D --> E["检查导出归档和源码 build"]
E --> F["针对确认对象清理或迁移"]

对应命令:

1
2
3
4
5
df -h /
sudo du -sh /var/lib/containerd /var/lib/docker
docker system df -v
docker buildx du
du -sh ~/images ~/work/demo-app/build 2>/dev/null

这五步基本能把“大空间去哪了”分成明确类别。

15. 最后再决定处理策略

如果主要是 BuildKit cache:

1
docker builder prune

如果是明确不需要的旧镜像:

1
docker image rm <image>

如果是离线归档:

1
2
转移到移动硬盘 / NAS
或确认备份后删除本地副本

如果是 containerd image store 的最终镜像本身:

1
它不是“垃圾缓存”

此时真正的解决方式可能是扩大数据分区、迁移 containerd 数据位置,或者重新评估是否必须长期保留如此巨大的完整 SDK 镜像。

关键原则只有一句:

先确认数据身份,再清理;不要用最激进的 prune 去替代诊断。

参考资料