DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Docker 拉取镜像报 no space left on device

镜像解压报 no space left on device,但磁盘仍有余量时,检查实际存储文件系统的 inode,定位占用并清理可重建缓存;迁移前区分 Docker 与 containerd 数据目录。

镜像层已经下载,解压或注册层时却失败:

failed to register layer: ... no space left on device

宿主机还有剩余容量,也可能出现这个错误。除了存放文件内容的空间,文件系统还需要 inode 来记录文件。包含大量文件的镜像解压后,可能先耗尽 inode,而不是用完字节容量。

下面的命令面向 Linux Docker Engine 宿主机,重点处理“磁盘字节仍有余量、inode 不足”的分支。如果两项都还有明显余量,就要继续核对报错涉及的挂载、配额或其他存储限制,不能只凭这条报错认定 inode 耗尽。

找到镜像实际写入的文件系统

先查看连接的 Docker 环境和服务端信息:

docker context show
docker info

关注 Docker Root Dir、存储驱动以及服务端版本。若客户端连接远端 daemon,后面的 df、du 必须在远端宿主机上运行;本机磁盘剩余多少与那次解压无关。Docker Desktop 的数据在其管理的虚拟环境中,也不能用 macOS 宿主机某个普通目录的统计代替。

对使用经典存储驱动、镜像数据位于 Docker 数据目录的实例,将下面的值替换为实际 Docker Root Dir:

docker_root='/var/lib/docker'
df -h "$docker_root"
df -i "$docker_root"

df 带路径时显示这个路径所在文件系统的统计。第一条看字节容量,第二条看 inode;不要只运行一次不带路径的 df,然后从众多挂载中随便挑一行。

如果 Avail 仍有余量,而 inode 表里的 IFree 已为零、IUse% 接近或达到 100%,就有了这一分支的直接证据。单看 df -h 的使用率不足以判断。

还需要区分 Docker 的存储方式:Engine 29.0 及以后的全新安装默认使用 containerd 镜像存储,镜像内容和容器快照默认在 /var/lib/containerd,其他 Docker 数据仍在 /var/lib/docker。从旧版本升级的实例可能继续使用经典驱动,不能只按版本号猜存储方式。已启用 containerd 镜像存储时,应检查它实际配置的存储目录;默认路径的检查示例是:

df -h /var/lib/containerd
df -i /var/lib/containerd

路径有定制时按实际配置替换。只检查 Docker 数据目录,可能漏掉真正耗尽 inode 的镜像存储。

查 inode 占用,不只查多少 GB

先看 Docker 管理的对象,再按目录核对文件数量方面的占用:

docker system df -v
sudo du --inodes --max-depth=1 -x "$docker_root"

第二条沿用前面的 docker_root 变量。若真正耗尽的是其他存储目录,应改为扫描那个目录。docker system df -v 提供镜像、容器、卷和构建缓存的使用信息,但它不是文件系统的 inode 余量统计。

GNU du --inodes 按 inode 使用情况统计目录,--max-depth=1 只显示这一层的汇总,-x 不进入其他文件系统。目录较多时,扫描仍会遍历许多文件,需要给它时间,避免在高负载时反复全盘扫描。

这一步用来缩小范围,并不意味着数字最大的目录可以删除。业务数据卷、容器可写层、镜像层和构建缓存的用途不同。若文件主要来自数据库或应用数据,应由对应业务确认保留规则;不能因为目录在 Docker 根目录下,就把它当成临时文件。

清理确认可重建的缓存

如果占用来自构建缓存,且已确认相关构建可以重新执行,可以先处理较旧的悬空构建缓存。例如:

docker builder prune --filter 'until=168h'

命令保留交互确认,不加 -f,也不加扩大清理范围的 -a。until=168h 是过滤示例,不是备份保留策略;实际阈值按构建用途调整。

示例针对 Docker 默认构建器的缓存。使用独立 Buildx builder 时,应先核对对应 builder 的占用;这里没有可清理内容,不代表其他 builder 的缓存也为空。Buildx 缓存统计可以按 builder 查看。

清理前确认缓存确实不承载必须保留的内容,构建所需源码、依赖和基础镜像仍能取得。被删除的缓存不能靠撤销命令恢复,后续构建可能需要重新下载和执行。这条命令也不保证一定能释放足够 inode;如果原本没有符合条件的缓存,就应换回占用定位,而不是不断扩大删除范围。

不要直接删除 overlay2、snapshotter 或 Docker 管理目录中的层文件。也不要把 docker system prune --volumes 当作看到这个错误后的固定操作:它会清理多种未使用对象,还涉及未使用匿名卷;停止的容器或未使用的卷也可能保存恢复所需内容。

迁移到 inode 余量充足的文件系统

如果把 data-root 改到 /home/docker,而 /home 与旧目录仍在同一个文件系统,改一个路径名并不会增加 inode。拟定迁移方案时,应对已经挂载好的目标目录同时运行 df -h 和 df -i,比较其文件系统来源、挂载点和余量。

对 inode 压力长期存在的实例,需要按镜像展开后的文件规模规划目标存储。只看压缩镜像的下载大小,无法得知这块文件系统还容不容得下那些文件。

data-root 是存储位置设置,不是自动搬运旧数据的命令。迁移需要维护窗口、相关业务备份、服务停止安排、正确保留文件属性的复制方案,以及启动后的镜像、容器和卷验收。只改成一个空目录,原有对象可能暂时不再显示,并不代表旧数据已经迁过去。

containerd 镜像存储还有单独的根目录配置;改 Docker 的 data-root 不会改变 /var/lib/containerd 中的镜像和容器数据位置。不要用只包含一个字段的 JSON 覆盖整份 daemon.json,也不要在未确认存储方式时把这项设置当作完整迁移方案。

迁移的回退需要保留旧配置和原数据目录,并避免新旧 daemon 共用同一个存储目录。数据库卷等业务数据若迁移后发生了写入,回退还必须处理这些新数据,不能直接指回旧副本。

重新拉取并观察 inode 余量

处理后,对真正写入镜像的文件系统重新运行 df -h 和 df -i,再重试原来失败的拉取或构建。需要确认的是层能完成解压、原任务能继续执行,同时 inode 和字节容量都有足够余量。

如果每次拉取大镜像都再次耗尽 inode,说明清理只暂时释放了一些空间,还需要调整镜像保留数量、构建缓存保留规则或存储容量。若重试依然失败但 inode 已恢复,则保留新报错及路径,继续查实际失败的文件系统,不要继续按 inode 耗尽的判断清理。