Docker 清理磁盘空间:VPS 满盘怎么安全释放
VPS 磁盘已满但 Docker 占用空间?先用 docker system df 区分镜像、容器、构建缓存和卷,再执行最小范围清理,避免误删数据。
清理之前,先找出磁盘空间被什么占用
Docker 会在 VPS 上的四个位置占用磁盘空间:镜像、已停止的容器、构建缓存和本地卷。先运行 docker system df,找出空间被哪一类对象占用,再执行能解决问题的最小范围清理命令。顺序很重要,因为本指南最后的命令 docker volume prune -a 会删除数据,而且无法撤销。
先检查文件系统,不要先检查 Docker。
df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -hdf 用于判断磁盘空间使用情况。du 用于确认空间被哪些目录占用。-x 标志会让 du 只在一个文件系统中统计,因此不会跟随挂载点进入独立卷,也不会重复统计。这里有五个重要目录:overlay2 存放镜像和容器层,volumes 存放卷数据,containers 存放容器元数据和日志文件,buildkit 存放构建缓存,image 存放层元数据。
下面说明 sudo 和 shell 通配符。这个问题经常浪费大量时间。/var/lib/docker 归 root 所有,普通用户无法读取,因此 ls /var/lib/docker 会返回 Permission denied。类似 sudo du -sh /var/lib/docker/* 的命令也会失败,因为 shell 会在 sudo 运行之前展开 *,而 shell 无法读取该目录。下面的每条命令都使用 find 或 --max-depth,而不是通配符,原因就在于此。
现在查看 Docker 自身的统计结果。
docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 6 12.4GB 8.91GB (71%)
Containers 31 6 1.42GB 1.39GB (97%)
Local Volumes 14 5 6.03GB 4.11GB (68%)
Build Cache 212 0 9.87GB 9.87GB这些数值来自一台机器,不能代表您的机器。应关注数据的构成。TOTAL 统计对象数量,ACTIVE 统计当前正在使用的对象数量,RECLAIMABLE 是 Docker 估算的该行对象可通过清理释放的空间。
RECLAIMABLE 有两个容易造成误解的地方。共享的镜像层会按使用它们的镜像分别计数,因此镜像行通常显示的可释放空间会大于实际释放量。它也从不包含容器日志文件,因为 Docker 不会将日志文件视为可回收对象。du 显示的目录明显大于 docker system df 报告的空间时,原因通常是日志文件。下文有专门的章节说明这一点。
添加 -v,查看每个对象的详细情况。
docker system df -v该命令会按对象类型分别显示摘要。镜像部分会添加 SHARED SIZE 和 UNIQUE SIZE 列,因此您可以看到单个镜像实际占用的空间。卷部分会添加 LINKS 计数,表示连接到该卷的容器数量。请记住 LINKS,因为值为 0 是卷清理命令执行前的完整判断条件。
无标签镜像与未使用镜像
这两个术语看起来可以互换,但实际并不是。由于对应的对象不同,过滤器的行为也不同。
无标签镜像是指没有标签的镜像。在 docker images 中,它显示为 <none>。每次重新构建时都会创建一个无标签镜像:docker build -t myapp:latest . 会将 myapp:latest 标签移到新镜像上,旧镜像保留所有层,但失去名称。没有任何对象引用它,也不会自动清理它。
未使用镜像是指当前没有容器引用的任何镜像,无论该镜像是否有标签。上个月拉取的 postgres:16 如果当前没有运行,就是未使用镜像,但不是无标签镜像。
docker image prune # dangling images only
docker image prune -a # every image no container refers to第二个命令会先询问确认。
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]请仔细阅读该提示。“与它们关联”指的是现有的容器对象,无论容器正在运行还是已停止。如果执行了 docker compose down,容器对象就会被删除,因此这些服务使用过的所有镜像现在都属于未使用镜像,-a 会将它们全部删除。不会丢失无法恢复的内容,但下一次执行 docker compose up -d 时会重新拉取或重新构建全部内容。在小型 VPS 上,这会消耗带宽和构建时间。因此,在清理镜像前,最好先了解docker compose down 会删除什么,以及 stop 会保留什么。
过滤器可以排除最近创建的镜像。
docker image prune -a --filter "until=240h"该命令会删除创建时间超过 240 小时(10 天)的未使用镜像,并保留较新的镜像。until 的值可以使用 Go duration 字符串,例如 240h,也可以使用绝对时间戳,例如 2026-08-01T00:00:00。
构建缓存是什么,以及它为什么会无限增长
自 Docker Engine 23.0 起,BuildKit 是 Docker 默认用于 docker build 和 docker compose build 的构建器。它会缓存所执行的每个 Dockerfile 步骤的结果,并将缓存保存在 /var/lib/docker/buildkit 中。缓存可以让第二次构建在几秒内完成,因此它确实发挥了作用。问题在于,默认情况下没有机制会使旧条目过期。如果使用每次都会变化的 COPY 步骤,将同一个镜像构建五十次,就会保留五组图层。
docker image prune 无法显示构建缓存。构建缓存是独立的对象类型,有自己的命令。
docker builder prune # dangling cache
docker builder prune -a # all unused cache
docker builder prune --filter until=168h # cache untouched for 7 days这些命令都不会影响您的镜像或数据。清除构建缓存唯一的代价是下一次构建会暂时变慢。在经常重新构建镜像的 VPS 上,Build Cache 通常是 docker system df 中占用空间最大的一项,因此也是最适合删除的大型对象。
按安全性从高到破坏性从强排列的清理命令
按此列表从上到下执行。只要 df -h / 恢复正常,就立即停止。每条命令完成后都会输出一行 Total reclaimed space:。
docker container prune删除已停止的容器。容器的可写层也会被删除,因此容器在卷之外写入的所有内容都会一并删除。不会处理卷。docker image prune仅删除悬空镜像。这是最安全的镜像清理命令。docker builder prune删除悬空构建缓存。代价是下一次构建会变慢。docker image prune -a删除所有没有容器引用的镜像。代价是重新拉取镜像或重新构建。docker system prune一次执行前三项操作,并额外删除未使用的网络。docker volume prune删除未使用的匿名卷。docker volume prune -a删除未使用的卷,包括命名卷。这条命令会删除数据库。
docker system prune 会在运行前说明自身的作用范围。
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache
Are you sure you want to continue? [y/N]列表中有意未包含卷。添加 --volumes 后,匿名卷也会纳入处理范围。添加 -a 后,镜像清理范围会从悬空镜像扩大到所有未使用的镜像。在生产主机上执行完整的 docker system prune -a --volumes -f,可能会在尝试释放空间时丢失数据。
为什么清理卷会删除数据库
本节内容需要读两遍。
当没有容器连接到某个卷时,该卷就会被视为未使用。这就是全部判断条件。Docker 不会检查卷是否为空、compose 文件是否仍声明该卷,也不会检查其中是否存放着数据库的唯一副本。LINKS 0 在 docker system df -v 中表示可清理,仅此而已。
现在连续执行两个普通操作。运行 docker compose down 可以干净地重启整个堆栈。该命令会删除容器,并保留命名卷,这正是文档所说明的行为。此时,Postgres 卷已不再连接到任何容器。10 分钟后,您运行 docker volume prune -a 释放空间,数据库就没了。两个命令都正常执行。是这两个操作的顺序销毁了数据。
从 Docker Engine 23.0(API version 1.42)开始,不带选项的命令比以前更有限。
WARNING! This will remove anonymous local volumes not used by at least one container.匿名卷是 Docker 为您创建的卷,通常是因为镜像声明了 VOLUME,而您没有为其指定名称。这类卷通常用于存放您并未要求保留的数据。命名卷,也就是您在 compose 文件中写入的卷,只有在添加 -a 后才会被删除。较旧版本的 Docker 使用不带选项的命令时会同时删除这两类卷,因此不要继续依赖升级前主机上的使用习惯。只有了解命名卷与绑定挂载的区别后,这一差异才有意义,因为绑定挂载根本不是 Docker 卷,任何 prune 命令都不会处理它。
删除前先检查。将 myapp_pgdata 替换为您要检查的卷名称。
docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_data卷上的 dangling=true 过滤条件表示未被引用,不表示为空。列出 _data 可以查看其中实际存放的内容。如果发现 pgdata 或 mysql 目录,请停止操作,并先创建副本。通过 docker compose down -v 也会造成同样的数据破坏;该命令会删除 compose 文件声明的所有卷,并且事先不会询问您。
在 Docker 主机上,卷是重建操作无法重新创建的对象。这就是为什么卷中的数据应存放在在服务器之外运行的 restic 备份中,这样即使误写选项,也无法影响这些数据。
容器日志文件:为什么清理不掉
您已经清理了所有内容,docker system df 几乎没有可回收空间,但磁盘仍然已满。请检查日志。
sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10每个容器都会将标准输出和标准错误写入 /var/lib/docker/containers/ 下的 JSON 文件。在默认安装中,max-size 未设置,这表示日志大小不受限制。因此,单个陷入崩溃重启循环的容器就可能一直写入日志,直到分区已满。没有任何 prune 命令会删除这些文件,因为生成这些文件的容器仍在运行,按定义它们不可清理。
不要删除该文件。对打开的日志文件运行 rm 不会释放任何空间,因为 Docker daemon 仍然持有打开的文件描述符,内核会继续保留这些块,直到该句柄关闭。df 完全不会移动。应改用 truncate,这会保留同一个 inode,并允许 daemon 继续写入。
sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /这只是临时措施。现在对这些容器运行 docker logs 不会返回任何内容,文件也会立即再次增长。真正的解决方案是配置日志轮换,下一节将介绍这一点。
每次都先测量,再验证结果
不要猜测清理操作的结果。先读取一次数据,运行一个命令,再读取一次数据。
df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /比较两次 df 的输出。只有这个数字能决定服务器是否还能继续提供服务。docker system df 会告诉您具体是哪一行发生了变化,每次清理操作也会输出自己的 Total reclaimed space: 数值。
如果 df 没有变化,但 docker system df 表示已释放空间,说明某个打开的文件句柄仍在占用已删除文件的磁盘块。这就是上文所述的日志文件问题。如果两者都发生了变化,但磁盘在一天内再次被填满,说明这是增长问题,而不是清理问题。解决方法是配置日志轮转并设置计划任务。
如何防止磁盘再次被占满
限制日志大小。 创建或编辑 /etc/docker/daemon.json。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}这会将每个容器的日志大小限制为 30 MB。log-opts 下的每个值都必须是字符串,包括数字值。重启前先检查文件是否能正确解析,因为格式错误的 daemon.json 会导致 daemon 完全无法启动,并使所有容器一同停止。
python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'现在,docker info 应报告 Logging Driver: json-file。在重启后创建的容器中,限制值会显示在 docker inspect 的 LogConfig 部分,这是需要注意的重点:此设置仅适用于新容器。现有容器会保留创建时使用的配置,因此需要重新创建它们。
docker compose up -d --force-recreate也可以在 compose 文件中按服务设置相同的限制。当某个日志量较大的服务需要单独设置限制时,这种方式更合适。
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"安排范围明确的清理任务。 每周执行一次,仅清理悬空镜像和旧的构建缓存。不要在计划任务中加入 -a 或 --volumes,因为任务在 stack 停止期间运行时,会删除该 stack 的镜像;如果使用 --volumes,它还会对您的数据执行操作。
sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-prune最后一行会先手动运行脚本一次,以便您在脚本无人值守运行前查看其输出。该文件必须具有可执行权限,文件名也不能包含点号,因为 run-parts 会跳过不可执行文件以及带扩展名的文件。
监控可用空间。 磁盘占满后执行清理属于恢复措施。在可用空间降至 80 percent 时发出警报,才是预防措施。
usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"将其加入 cron,并使用您现有的通知程序。可用空间只是问题的一部分,因此还应将此警报与 VPS 磁盘健康监控结合使用,因为磁盘故障和磁盘占满都会导致容器停止,但两者需要不同的处理方法。
以上内容都假设使用标准安装,并将数据根目录设为 /var/lib/docker。如果您通过 daemon.json 中的 data-root 键修改了数据根目录,请在每条命令中替换为您的路径。在新服务器上正确设置此目录布局,是在 VPS 上设置 Docker的一部分;在错误分区上已经存放 40 GB 容器后再决定,处理起来会困难得多。
FAQ
Docker system prune 会删除我的卷吗?
不会。直接运行该命令会删除已停止的容器、未使用的网络、悬空镜像和未使用的构建缓存,确认提示会准确列出这些对象。只有添加 --volumes 后,卷才会被纳入范围;自 Docker Engine 23.0 起,该标志针对的是匿名卷,而不是命名卷。命名卷会被 docker volume prune -a 和 docker compose down -v 删除。这两个命令需要特别谨慎。
运行 docker prune 后,为什么磁盘仍然已满?
通常有两个原因。第一,容器日志文件位于 /var/lib/docker/containers/ 下。prune 命令不会处理这些文件,它们会无限增长,直到设置 max-size。第二,某个进程仍然打开着已删除的文件:如果容器仍在运行时使用 rm 删除了日志,daemon 会继续保留文件描述符,内核也不会释放这些块,因此 df 的结果不会变化。对比 sudo du -xh --max-depth=1 /var/lib/docker 和 docker system df,即可确定属于哪种情况。
docker image prune 和 docker image prune -a 有什么区别?
直接运行该命令只会删除悬空镜像,也就是已丢失标签的镜像,这通常是重建镜像造成的。-a 形式会删除所有没有现有容器引用的镜像,包括您主动拉取的带标签镜像。执行 docker compose down 后,容器已经被删除,因此 -a 会连同该堆栈的镜像一起删除。数据不会永久丢失,因为下次启动时会重新拉取或重新构建这些镜像;但在网络速度较慢时,这会等待很长时间。
如何防止 Docker 日志占满磁盘?
在 /etc/docker/daemon.json 的 log-opts 下设置 max-size 和 max-file,然后使用 sudo systemctl restart docker 重启 daemon。这些设置只对重启后创建的容器生效,因此需要使用 docker compose up -d --force-recreate 重新创建正在运行的容器。您也可以在 compose 文件中,在 logging 键下为每个服务设置相同的两个选项。当某个服务的日志量远高于其他服务时,应使用这种方式。
在 cron 任务中运行 docker system prune 安全吗?
如果主机上的所有堆栈都持续运行,直接运行 docker system prune -f 通常是安全的;但它会删除已停止的容器,因此也会删除您有意停止、准备稍后重新启动的容器。更安全的定时任务是 docker image prune -f 加 docker builder prune -f --filter until=168h。它会释放增长最快的两类资源,并且不会接触卷。绝不要调度 -a 或 --volumes。