跳至主要内容

如何修复 Docker “No Space Left on Device”:完整指南

掌握 Docker 故障排除,解决“No space left on device”错误。学习如何安全清理未使用的容器并回收丢失的存储空间。
· 13分钟 阅读

使用 AI 探索

Docker 出现“No space left on device”错误,往往发生在最糟糕的时机:部署中途、关键构建期间,或拉取重要镜像时。我也多次遇到过这种情况,可以肯定地说,在未正确诊断前就匆忙删除文件,极易导致数据丢失。

这类错误之所以棘手,往往在于它很少只有单一原因。可能是堆积的 Docker 镜像、失控的容器日志、数百万个小文件耗尽了 inodes,或者是被运行进程占用的已删除文件造成的“幽灵”空间。每种情况都需要不同的解决方案。

因此我总是强调:先诊断,再行动。弄清根因能帮助您更高效地采取针对性措施,并避免干扰生产环境。本文将带您系统化地完成诊断、解决与预防这一恼人问题。

如果您是 Docker 新手,建议先学习我们的动手课程Introduction to Docker,它涵盖容器化入门所需的一切。

什么是 Docker “No Space Left on Device” 错误?

当 Docker 抛出该错误时,表明系统存储出现以下两类根本问题之一:

  • 物理磁盘块耗尽
  • inode 耗尽

理解二者区别对于采用正确的解决方案至关重要。

导致“No space left on device”错误的根因

首个、也是最直观的原因是物理磁盘块耗尽。文件系统已没有实际可写入的数据空间。这是更容易理解的情形:磁盘被 Docker 镜像、容器、日志或其他文件占满了。

第二个不那么明显的原因是inode 耗尽。即便仍有数 GB 的可用空间,文件系统也可能耗尽 inodes(用于跟踪文件和目录的元数据结构)。每个文件和目录都会消耗一个 inode,因此当应用创建数百万个小文件(如 PHP 会话文件或 npm 的 node_modules 目录)时,inodes 会被耗尽,而磁盘空间仍有剩余。

了解 overlay2 存储驱动

Docker 通常使用基于 Linux OverlayFS 的 overlay2 存储驱动。OverlayFS 会在单个主机上叠加多个目录,并呈现为统一的文件系统。镜像层以只读的 lower 目录挂载,而每个正在运行的容器会叠加一层很薄的可写 upper 层。容器看到的是合并后的单一目录视图。

OverlayFS

overlay2 理论上原生支持最多 128 个 lower 层,但 Docker 的层存储对每个镜像的基底层实际限制为 125 个。它实现了高效的镜像组合,并提升了诸如 docker build 和 docker commit 等操作的性能。 

尽管 overlay2 的设计比早期存储驱动更少消耗 inodes,但在频繁构建镜像、拉取层或创建容器的环境中,随着时间推移,尤其当镜像或应用层包含大量小文件时,仍会对磁盘块和 inodes 造成显著压力。

该错误的表现因部署环境而异:

  • 在原生 Linux 系统上,它影响支撑 /var/lib/docker 的文件系统。

  • 在Windows 或 macOS 的 Docker Desktop上,限制存在于 Docker 的虚拟机磁盘镜像文件(如 .raw 或 .vhdx)内,回收或调整存储大小时还需额外考虑。

步骤一:诊断根因

在删除任何内容之前,我总会先进行全面诊断。花几分钟做这件事,能为您节省数小时的排错时间,并避免意外的数据丢失。

检查系统层面的容量

先用 df -H(df 即 disk free)检查主机磁盘使用情况:

df -H

Free Disk Check in WSL

WSL 中检查可用磁盘

该命令以人类可读的格式显示磁盘使用情况。重点关注接近或达到 100% 容量的文件系统。

在原生 Linux 安装中,尤其要留意 /var/lib/docker 所在的分区。它通常位于根分区(/)或专用的 Docker 分区。

在 Docker Desktop(Windows/Mac)中,请关注主要的文件系统挂载(通常为 /dev/sdf 或类似项),它承载 Docker VM 的数据,而非特定的 /var/lib/docker,因为 Docker 运行在虚拟机内。

接着使用 df -i 检查 inode 是否耗尽:

df -i

Inode Exhaustion Check

检查 inode 是否耗尽

如果某个文件系统的 inode 使用率(IUse%)为 100%,问题就找到了。这在构建服务器和 CI/CD 环境中非常常见,Docker 会反复创建和销毁包含大量小文件的容器。

指标

命令

关注点

磁盘空间

df -H

使用率 90%+ 的分区

Inode

df -i

IUse% 达到 100%

Docker 目录

du -h /var/lib/docker

总占用大小

分析 Docker 专项使用情况

确定是磁盘空间还是 inode 问题后,进一步审查 Docker 的资源消耗。docker system df 提供在所有 Docker 安装中通用的高层摘要:

docker system df

该输出按四类进行空间占用细分:镜像、容器、本地卷和构建缓存。

RECLAIMABLE 列尤为重要。它显示在不影响正在运行的容器前提下,您可以回收的空间量。理解两个指标的区别至关重要:“active” 表示当前被运行的容器使用,“reclaimable” 则表示可以安全释放。

如需更细粒度的细节,可添加 verbose 标志:

docker system df -v

Docker Usage Analysis

Docker 使用情况分析

详细输出会逐一列出每个镜像、容器和卷及其大小,帮助您定位“空间大户”。各部分关注要点如下:

  • Images:显示每个镜像大小及是否在用。留意未使用或以不同标签重复的超大镜像。标记为“unused”的镜像可安全删除,不会影响正在运行的容器。

  • Containers:展示每个容器可写层的大小。若某个已停止容器的 SIZE 很大,说明它向文件系统写入了大量数据。CREATED 列可帮助识别可能被遗忘的旧容器。

  • Volumes:列出卷大小及是否在用。未使用的卷可以作为删除候选,但清理前务必确认其中不含重要数据。

  • Build Cache:常见的最大消耗者且易被忽略。它们是 docker build 生成的中间层,用于加速后续构建。

操作系统专项命令

对于原生 Linux 用户,如需更深入的文件系统洞察,可直接检查子目录,查看哪些子目录(overlay2、containers、volumes)占用最多空间:

sudo du -h --max-depth=1 /var/lib/docker | sort -h

不过,对于 Docker Desktop 用户,/var/lib/docker 位于 Docker 的隐藏虚拟机中,无法直接访问。好消息是,无论平台如何,docker system df -v 都能提供所有可执行的信息,是最可靠的诊断方式。

步骤二:使用 Docker Prune 清理系统 

确定空间消耗位置后,可用 Docker 内置的清理命令安全回收空间。这些操作旨在仅移除未使用的资源,从而尽量降低对正在运行服务的影响。

清理悬空资源

Docker 区分“dangling”(悬空)与“unused”(未使用)资源。悬空镜像没有标签,也不被任何容器引用,通常是失败或已被取代的构建中间层。这类镜像始终可以安全删除。

从基础的prune 命令开始:

docker system prune

此命令会移除所有未使用的容器、网络和悬空镜像。除非明确指定,否则不会动带标签的镜像或卷。执行前 Docker 会提示确认。

如需更激进地删除所有未使用的镜像(不只是悬空的),请添加 --all 标志:

docker system prune --all

Docker System Prune

Docker system prune

该命令会删除未与任何容器关联的镜像。请谨慎:后续部署需要重新拉取被删除的镜像。

如果要将卷也纳入清理(卷包含持久化数据),请添加 --volumes 标志:

docker system prune --all --volumes

警告:这将永久删除未使用卷中的数据。使用该标志前请务必确认卷中不包含重要数据。

管理构建缓存

重点在此:构建缓存会存储 Docker 构建操作的中间层,往往是最大的空间消耗者,但 docker system prune 会有意跳过它,以保留构建性能。

要专门清理构建缓存:

docker builder prune

如果您想有选择地保留近期缓存层,可用基于时间的过滤器:

docker builder prune --filter "until=24h"

这将仅删除早于 24 小时的缓存层,保留近期工作。

安全清理卷

卷需要格外谨慎,因为其包含持久化数据,如数据库、上传文件和应用状态。删除错误的卷会造成永久性数据丢失。

首先识别悬空卷(未附加到任何容器的卷):

docker volume ls -f dangling=true

请仔细核查列表。如果确定这些卷不再需要,执行清理:

docker volume prune

Docker 会在执行前提示确认。如有不确定,先备份卷再清理。

步骤三:处理 Docker 日志文件膨胀

容器日志可能会在不知不觉中占用大量磁盘空间,有时单个容器就会超过 50 GB。

定位膨胀的日志文件

Docker 默认的 json-file 日志驱动会将容器的 stdout 和 stderr 捕获为 JSON 文件。若未配置最大大小(默认 -1,即无限制),这些文件会无限增长。

在所有环境中检查容器大小:

docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Size}}"

检查特定容器的日志文件大小:

ls -lh $(docker inspect --format='{{.LogPath}}' <container-name>)

仅适用于原生 Linux,直接查找最大的日志文件:

sudo find /var/lib/docker/containers/ -name "*-json.log" -exec du -h {} + | sort -h | tail -10

如果 docker logs 变慢或容器无法启动,很可能是日志过大所致。此时可通过截断日志来解决。

安全地截断日志

截断日志可立即释放空间。但切勿在容器运行时用 rm 删除日志文件。Docker 守护进程会持有这些日志文件的打开句柄,直接删除会产生“已删除但仍打开”的文件,直到守护进程重启前都仍占用空间。

安全做法是将日志截断为 0:

sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container-name>)

这会立即释放空间且不会破坏文件句柄。容器会继续往同一文件写入。

步骤四:高级排障与隐性问题

若常规清理无效,您很可能遇到了以下不常见但影响同样巨大的问题:

  • inode 耗尽:尽管仍有可用磁盘空间,但文件系统的文件元数据结构已耗尽

  • 已删除但仍打开的文件:进程持有已删除文件的句柄,导致“幽灵”空间占用

  • Docker Desktop 虚拟磁盘膨胀:在 VM 内删除数据后,宿主机上的 .raw 或 .vhdx 文件并不会自动缩小

我们分别系统地处理这些情形。

解决 inode 耗尽

这通常由应用创建海量小文件引起,例如 PHP 会话、npm 的 node_modules 或构建产物,都会以惊人的速度消耗 inodes。

首先确认 inode 是否耗尽:

df -i

使用 Docker 命令(适用于所有环境):

docker ps -a | wc -l             # 检查容器数量
docker images | wc -l         # 检查镜像数量
docker image prune -a      # 移除未使用镜像以释放 inodes

对于 Linux 安装,定位 inode 密集的目录:

for dir in /var/lib/docker/*/; do
    echo "$dir: $(sudo find $dir -type f 2>/dev/null | wc -l) files"
done

用 docker rmi 删除未使用的镜像也可释放 inodes。若是应用层面的问题,请在容器内实现清理机制,或将临时文件改用卷挂载。

处理已删除但仍打开的文件(仅限 Linux)

当某个文件在仍被进程打开时被删除,文件系统不会释放空间,直到进程关闭该文件句柄(形成“幽灵”空间占用)。

Linux 用户可以通过在打开文件列表中搜索“deleted”条目来验证是否发生了这种情况。

首先识别这些文件:

sudo lsof | grep deleted 

Docker Deleted Files

Docker 已删除文件

然后,重启对应进程(使用 lsof 输出的 PID)或重启 Docker 以释放空间。完成后再次运行该命令,若不再出现这些文件,说明已被正确移除。

接下来探讨最后一个仅适用于 Docker Desktop 的高级排障策略:管理虚拟磁盘。

管理 Docker Desktop 虚拟磁盘

Docker Desktop 会将数据存储在一个虚拟磁盘文件中(Mac 为 .raw,Windows 为 .vhdx),该文件会按需增大,但当您在 VM 内删除数据时并不会自动缩小。您可能清理了镜像和容器,在 VM 内看到可用空间增加,但宿主机磁盘仍然很满。

为此,最安全的做法是使用 fstrim 在不丢失数据的情况下压缩磁盘:

docker run --privileged --pid=host alpine nsenter -t 1 -m -u -i -n fstrim /

该命令会修剪 Docker Desktop VM 的文件系统,将未使用的块回收至宿主机。您的镜像、容器和卷均保持不变。

如果您希望完全从头开始(这会删除所有数据),也可以通过Docker Desktop Settings → Troubleshoot → Reset to factory defaults 来实现。

警告:这是破坏性操作,将移除所有镜像、容器、卷和设置。仅在其他方法全部无效时作为最后选项。

Reset to factory defaults

恢复出厂设置

步骤五:预防 Docker “No Space Left on Device” 错误

在解决燃眉之急后,我们来实施结构性改进以防复发。这些配置需要规划,但能显著提升系统稳定性。

移动 Docker 根目录(仅限 Linux)

Docker Desktop 用户无法轻松更改数据根位置。可选择:

  • 增加磁盘空间:使用步骤四所述的虚拟磁盘压缩方法

  • 对于 WSL2 后端:通过 .wslconfig 文件进行管理,路径见 Docker Desktop Settings → Resources

但如果您在 Linux 上,且根分区受限,而另有更大的分区(如专用数据盘),将 Docker 数据目录迁移过去是一个长期方案。

首先停止 Docker:

sudo systemctl stop docker

编辑 /etc/docker/daemon.json 指定新位置:

{
  "data-root": "/mnt/docker-data"
}

迁移现有数据以保留镜像和容器:

sudo rsync -aP /var/lib/docker/ /mnt/docker-data/

最后重启 Docker:

sudo systemctl start docker

Docker 现已在新位置存储所有数据。可通过 docker info | grep "Docker Root Dir" 验证新目录路径。

配置全局日志轮转

日志轮转是我实施过的最有效预防策略。默认情况下,Docker 的 json-file 驱动 max-size: -1(无限制),在生产环境中极易埋下隐患。

对于原生 Linux 安装,在 /etc/docker/daemon.json 中全局施加限制:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

修改完成后重启 Docker。

对于 Docker Desktop 用户,不要直接编辑 /etc/docker/daemon.json。请按以下步骤:

  1. 打开 Docker Desktop
  2. 进入 Settings → Docker Engine
  3. 您会看到一个 daemon 配置的 JSON 编辑器
  4. 在现有 JSON 中添加日志轮转设置:
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

最后点击Apply & Restart。

该示例将每个容器的日志限制为 3 个文件、每个 10MB(总计 30MB)。

重要提示:这些设置仅对新创建的容器生效。现有容器保留原始日志配置,需重新创建容器以应用新限制。

如需更佳的压缩与性能,可考虑使用 local 日志驱动:

{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "compress": "true"
  }
}

此外,您也可以在 docker-compose.yml 中为每个服务覆盖日志设置:

services:
  web:
    image: nginx
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

自动化维护

另一个良好做法是将部分流程自动化。常言道,预防胜于危机处理。因此,我建议在维护时段通过 cron 任务自动清理:

# Create a cleanup script
sudo cat > /usr/local/bin/docker-cleanup.sh << 'EOF'
#!/bin/bash
docker system prune -f
docker builder prune -f --filter "until=168h"
EOF

sudo chmod +x /usr/local/bin/docker-cleanup.sh

# Add to crontab to run weekly on Sundays at 2 AM
echo "0 2 * * 0 /usr/local/bin/docker-cleanup.sh" | sudo crontab -

上述脚本会创建一个计划任务,每周日凌晨 2 点自动移除已停止的容器和未使用的数据。同时,它也会安全清理一周(168 小时)以前的构建缓存层,防止磁盘使用无限增长。

此外,请通过多阶段构建优化 Dockerfile,以最小化镜像大小。多阶段构建允许在一个阶段中编译/构建(包含所有开发工具),然后仅将最终产物复制到干净、精简的生产阶段。

这样可将构建依赖、源代码和中间文件排除在最终镜像之外,显著缩减体积并降低攻击面。

# Build stage
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Production stage
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

该模式通过排除构建工具和源代码来缩小生产镜像。

结语

修复“No space left on device”遵循可预期的流程:先诊断,明确是磁盘块还是 inodes;再安全清理,在不丢数据的前提下回收空间;最后优化配置,预防复发。

多年管理 Docker 环境的经验告诉我,日志轮转是最有效的预防策略。一个简单的 daemon.json 配置,足以避免大多数空间耗尽事故。

我建议您现在就审查 daemon.json 配置。如果尚未为日志驱动设置 max-size 和 max-file,请立即添加。当您成功避开下一次空间危机时,会感谢现在的自己。

准备好成为 Docker 高手了吗?下一步,报名我们的Containerization and Virtualization with Docker and Kubernetes 技能路径,我强烈推荐。

Docker “No Space Left on Device” 常见问答

是什么导致 Docker 出现“No space left on device”错误?

该错误源于两类问题之一:物理磁盘耗尽(被镜像、容器或日志占满)或 inode 耗尽(过多小文件消耗文件元数据)。Docker 的 overlay2 存储驱动会累积层,在构建频繁的环境中会快速消耗磁盘空间与 inodes。

如何识别哪些 Docker 资源占用了最多磁盘空间?

使用 docker system df 查看镜像、容器、卷和构建缓存的空间占用细分。运行 docker system df -v 可获取详细信息,逐项列出每个资源及其大小。RECLAIMABLE 列指示可安全回收的空间量。

如何在不丢失重要数据的情况下,安全清理 Docker 所占磁盘空间?

先运行 docker system prune 删除已停止的容器、未使用的网络和悬空镜像。若需更彻底清理,使用 docker system prune -a 删除所有未使用的镜像。切勿在未核实卷数据安全的情况下使用 --volumes 标志,因为这会永久删除卷中的数据。

如何防止 Docker 日志文件占满磁盘空间?

在 daemon.json(或在Docker Desktop Settings → Docker Engine)中配置全局日志轮转,设置 "max-size": "10m" 与 "max-file": "3"。这会将每个容器的日志总量限制在 30MB。请记住,这些设置仅对新建容器生效,现有容器需要重建。

如何防止 Docker 的 inode 被耗尽?

inode 耗尽发生在应用创建数以百万计的小文件时。用 df -i 检查 inode 使用情况,并通过 docker image prune -a 删除未使用镜像以降低使用量。对于会产生大量临时文件的应用(如 npm 或 PHP 会话),请将临时存储放在卷挂载中,而非写入容器的可写层。

Benito Martin

作者

Benito Martin

作为 Martin Data Solutions 的创始人兼自由职业数据科学家、ML 与 AI 工程师,我在回归、分类、NLP、LLM、RAG、神经网络、集成方法和计算机视觉等领域拥有多元项目经验。

  • 成功交付多项端到端机器学习项目,覆盖数据清洗、分析、建模与在 AWS 和 GCP 上的部署,提供具备影响力且可扩展的解决方案。
  • 使用 Streamlit 和 Gradio 构建交互式、可扩展的网页应用,服务于不同行业场景。
  • 教授并指导数据科学与数据分析学员,通过个性化学习路径促进其职业成长。
  • 为企业级需求设计检索增强生成(RAG)应用课程内容。
  • 撰写高影响力的 AI 与 ML 技术博客,涵盖 MLOps、向量数据库和 LLM 等主题,并获得显著关注。

在我参与的每个项目中,我都会应用最新的软件工程与 DevOps 实践,如 CI/CD、代码规范检查、格式化、模型监控、实验追踪以及健壮的错误处理。我致力于交付完整的解决方案,将数据洞察转化为切实可行的策略,帮助企业成长,并充分发挥数据科学、机器学习与 AI 的价值。

主题