Docker prune:从入门到精通
为什么需要Docker Prune?
Docker虽然好用,但也带来了隐形成本:随着时间推移,镜像、容器、卷和网络会不断堆积,悄悄吃掉大量磁盘空间。这并不是Docker的设计缺陷,而是其核心设计理念的结果。Docker 在管理资源时天生就比较"保守",它不会擅自删除任何东西,这就把清理系统的"烂摊子"甩给了用户。如果不主动清理,你的服务器硬盘很快就会被撑满。
五种最占空间的东西
理解哪些东西最占空间是有效清理空间的第一步。以下是五个主要的"垃圾"来源:
-
已停止的容器 (Stopped Containers):
每次运行容器(即使只是一次性命令),Docker 都会创建一个容器实例。这些容器在停止后并不会自动消失,它们依然保留着自己的文件系统层,占用着空间。 -
悬空镜像 (Dangling Images):
这是最常见的空间浪费源。当你重新构建一个已存在的镜像时(例如,docker build -t my-app .
),旧版本的镜像并不会被删除,而是会失去它的标签,变成一个"悬空"或"没人要"的镜像,在列表中显示为<none>:<none>
。这些镜像不再被任何容器使用,纯粹是历史遗留问题。 -
未使用的镜像 (Unused Images):
这比悬空镜像的范围更广。一个镜像可能有一个响亮的标签(如ubuntu:20.04
),但如果没有任何容器(无论是运行中还是已停止的)在使用它,那它就是一个未使用的镜像。你可能下载了很多基础镜像或测试镜像,用完后就忘了删。 -
未使用的卷 (Unused Volumes):
卷是用来持久化存储数据的。但如果你创建的容器使用了卷,后来又把容器删了,那个卷并不会被自动删除。特别是"匿名卷"(没有名字的卷),它们很容易被遗忘,像数字世界的野草一样疯长。 -
未使用的网络 (Unused Networks):
与卷类似,当你为特定应用创建了自定义网络,但之后删除了所有使用该网络的容器时,这个网络本身并不会消失。虽然单个网络占用的空间不大,但日积月累也很烦人。
不定期清理这些用不上的东西,不仅能释放大量磁盘空间,还能让你的Docker管理更方便,避免在查找和管理时像"翻箱倒柜找东西"一样困难。
docker prune
命令
为了解决上述问题,Docker 提供了一套强大的 prune
(意为"修剪")命令。这些命令就像瑞士军刀,每把刀都有特定的用途。
docker system prune
这是清理工具里最厉害的"大扫除"工具,也是最常用的。它会一次性删除所有以下未使用的资源:
- 所有已停止的容器
- 所有悬空镜像
- 所有未使用的网络
- 所有构建缓存
默认情况下,docker system prune
不会删除未使用的卷,这是一个非常重要的安全设计,防止你一不小心把重要数据给删了。
# 执行一次全面的系统清理
docker system prune
# 加上 -a 或 --all 参数,清理得更"狠",会删除所有未使用的镜像(不只是悬空的)
docker system prune -a
# 加上 --volumes 参数,会连同未使用的卷一起删除
docker system prune --volumes
警告:
docker system prune --volumes
是一个危险的操作。它会删除所有没有被任何容器引用的卷,包括那些你可能还想保留的、存有重要数据的命名卷。使用前请三思。
清理特定的东西
如果你不想用"大扫除"命令,只想清理某一种特定的资源,可以使用下面这些更精细的命令。这样做的好处是目标明确,能减少误删的风险。
docker container prune
只删除所有已停止的容器。
docker container prune
docker image prune
默认只删除悬空镜像 (<none>:<none>
)。
# 只删除悬空镜像
docker image prune
# 使用 -a 或 --all 参数,删除所有未被任何容器使用的镜像(包括有标签的)
docker image prune -a
docker volume prune
只删除未使用的卷。
docker volume prune
再次警告: 这个命令非常危险,删了就真没了。Docker 的设计思路是"数据至上",所以它默认不会碰你的卷。在你运行这个命令之前,请确保你真的知道自己在做什么,并且已经备份了重要数据。
docker network prune
只删除所有未使用的网络。
docker network prune
安全清理的正确顺序
Docker 的资源之间存在依赖关系:容器依赖镜像,卷和网络被容器使用。因此,一个安全的清理顺序应该是:
- 停止并删除容器 (
docker stop
/docker rm
或docker container prune
)。 - 删除不再需要的镜像 (
docker image prune
)。 - 删除不再需要的卷 (
docker volume prune
)。 - 删除不再需要的网络 (
docker network prune
)。
docker system prune
基本上就是帮你按这个顺序自动执行了一遍。
玩转清理命令:--all
和 --filter
精确操作
仅仅知道基础命令是不够的,要成为 Docker 清理大师,你必须掌握那些能让你精准控制的参数。
“一键清理"类参数的威力与风险
--all
(-a
) 和 --volumes
是 docker prune
命令里最有杀伤力也最容易被搞错的参数。
-
--all
(-a
):
这个参数用在docker image prune
上时,会将清理范围从"悬空镜像"扩大到"所有未使用的镜像”。这意味着,即使一个镜像有名字(比如nginx:latest
),只要没有任何容器在用它,它就会被删除。这对于清理那些拉下来测试后就没再用过的镜像非常有用。 -
--volumes
:
这个参数专属于docker system prune
。它就像一个开关,一旦打开,system prune
就会在清理时把未使用的卷也一并带走。再次强调,这可能会导致数据丢失。 -
--force
(-f
):
这个参数会跳过"你确定吗?“的确认步骤,直接执行删除操作。它在自动化脚本里很有用,但手动操作时要格外小心,因为你连后悔的机会都没有。
使用 --filter
:过滤操作的工具
--filter
参数让 docker prune
命令从一个"大锤"变成了一把"手术刀”。你可以用它来设定非常具体的规则,只删除符合特定条件的资源。
最常用的过滤器是 until
和 label
。
按时间过滤 (until
)
你可以删除某个时间点之前创建的资源,until
的值可以是一个时间戳,也可以是一个时间段(比如 24h
)。
# 删除所有已停止超过24小时的容器
docker container prune --filter "until=24h"
# 删除所有超过10天未使用的镜像
docker image prune -a --filter "until=240h"
# 删除所有超过7天未使用的构建缓存
docker builder prune --filter "until=168h"
按标签过滤 (label
)
这是最强大的保护机制。你可以给不想被误删的资源打上一个特殊的"保护"标签,然后在清理时告诉 Docker “不要动有这个标签的东西”。
这种"排除法"的思路是一个巨大的转变。它让你从"看到什么删什么"的模式,变成了"没贴标签的才删"的模式,大大提高了安全性。
# 1. 创建一个带保护标签的卷
docker volume create --label keep=true important_data
# 2. 删除所有没有 "keep=true" 标签的未使用卷
# 注意 "label!=key=value" 这个语法
docker volume prune --filter "label!=keep=true"
# 3. 保护镜像也是同理
docker tag my-image my-image:latest
docker label "my-image:latest" keep="true" # 注意:Docker 没有直接给镜像打标签的命令,这通常在构建时用 LABEL 指令完成
docker image prune -a --filter "label!=keep=true"
掌握了过滤器,你就可以放心地在自动化脚本中使用 docker prune -f
了,因为你知道它只会删除那些你明确允许删除的东西。
docker prune 使用指南
知道命令怎么用是一回事,知道什么时候用、怎么安全地用是另一回事。
清理前的安全检查清单
在执行任何 prune
命令之前,特别是带 --force
的时候,请务必过一遍这个清单:
-
先大概看看情况:
docker ps -a
:看看有哪些容器,它们的状态是什么。docker images -a
:看看你有哪些镜像,哪些是悬空的。docker volume ls
:看看有哪些卷,特别是那些名字很奇怪的匿名卷。docker network ls
:看看有哪些网络。
-
手动检查一下:
对于那些你不确定的资源,比如一个看起来像是重要数据的卷,用docker volume inspect <volume_name>
检查一下它的详细信息。 -
先演练,再执行:
在不加--force
的情况下运行命令,看看 Docker 提示将要删除哪些东西。确认无误后,再用--force
执行(如果需要的话)。 -
备份!备份!备份!
对于生产环境或者任何存有重要数据的卷,删除前一定要先备份。删了就真的找不回来了。
docker prune 注意点
在开发环境里,你可以大胆地用 docker system prune -a --volumes
来释放空间。但在生产环境里,这么做无异于玩火。生产环境的核心原则是"稳定压倒一切"。
警告: 不要在生产服务器上随意运行
docker system prune
,尤其是在你不完全清楚服务器上运行着哪些应用和数据时。
生产环境的清理策略应该是:
- 宁可一个个删,不要一锅端: 尽量使用更精确的
container prune
、image prune
等命令,并且配合--filter
使用。 - 自动化要格外小心: 如果你必须在生产环境自动化清理,一定要使用带有"保护"标签的过滤器,确保核心应用和数据绝对不会被误删。
- 理解应用,而不是依赖工具:
docker prune
是一个一根筋的工具,它只看资源"有没有被引用",而不管这个资源对你的业务是否重要。清理前,你必须从应用的层面去理解每个卷、每个镜像的作用。
在 CI/CD 流水线中使用docker prune自动化清理
CI/CD 环境是 Docker 产生垃圾的重灾区。每次构建、每次测试都会产生大量的临时镜像和容器,如果不加以控制,CI/CD 服务器的磁盘很快就会爆炸。
为什么 CI/CD 中需要清理?
一个典型的 CI/CD 流程可能包括:
- 拉取最新的代码。
- 使用
Dockerfile
构建一个新镜像。 - 用这个新镜像启动容器来跑测试。
- 测试通过后,将镜像推送到仓库。
在这个过程中,至少会留下一个悬空镜像(旧版本的镜像)和一个已停止的容器。日积月累,数量惊人。因此,在 CI/CD 流水线的末尾加入清理步骤至关重要。
GitLab CI/CD 中的清理示例
在 GitLab CI/CD 中,你可以在 .gitlab-ci.yml
文件的 after_script
部分加入清理命令。after_script
里的命令无论流水线成功还是失败都会执行,非常适合做清理工作。
stages:
- build
- test
- cleanup
build_job:
stage: build
script:
- docker build -t my-app:$CI_COMMIT_SHA .
- docker push my-app:$CI_COMMIT_SHA
test_job:
stage: test
script:
- docker run my-app:$CI_COMMIT_SHA npm test
cleanup_job:
stage: cleanup
script:
# 只清理超过1小时的资源,给并行任务留出足够的时间
- docker system prune -a -f --filter "until=1h"
# 为了更安全,也可以只清理悬空镜像和已停止的容器
# - docker image prune -f
# - docker container prune -f
# 确保这个任务总会运行
when: always
注意: 使用
until
过滤器是一个好习惯,可以避免清理掉那些可能还在被其他并行任务使用的资源。
构建缓存的取舍:要快还是要干净?
docker system prune
会清除构建缓存,这意味着下次构建会从零开始,速度会变慢。这是一个典型的"性能"与"干净"之间的权衡。
- 要性能: 保留构建缓存,加快构建速度。但磁盘占用会持续增长。
- 要干净: 每次都清理构建缓存 (
docker builder prune
)。磁盘占用低,但每次构建都慢得像第一次。
最好的折中办法是定期清理,而不是每次都清理。例如,你可以设置一个每周运行一次的定时任务,专门用来清理旧的构建缓存。
# 只删除超过一周未使用的构建缓存
docker builder prune --filter "until=168h"
dockre prune 与其他命令比较
docker-compose down
vs. docker system prune
docker-compose
用户经常会纠结:我该用 docker-compose down
还是 docker system prune
?它们有很大不同。
特性 | docker-compose down |
docker system prune |
---|---|---|
范围 | 项目范围:只管理由当前 docker-compose.yml 文件定义和创建的资源。 |
系统范围:管理宿主机上所有的 Docker 资源,不管它们是怎么创建的。 |
默认行为 | - 删除容器 - 删除网络 |
- 删除已停止的容器 - 删除悬空镜像 - 删除未使用的网络 |
卷的处理 | 默认不删除卷。需要加 -v 或 --volumes 参数才会删除。 |
默认不删除卷。需要加 --volumes 参数才会删除。 |
镜像的处理 | 默认不删除镜像。需要加 --rmi all (删除所有) 或 --rmi local (只删除没有自定义标签的) 参数才会删除。 |
默认只删除悬空镜像。加 -a 才删除所有未使用的。 |
一个典型的作死现场:
你有一个用 docker-compose
启动的重要应用(比如数据库),它使用了一个命名卷 my_db_data
。后来你为了清理磁盘,在另一个项目目录或者根目录下运行了 docker system prune --volumes
。由于 docker-compose
的容器当时可能已经停止了,prune
命令会认为 my_db_data
是一个"未使用"的卷,然后毫不留情地把它删掉。
你得这么想:
docker-compose down
是用来拆除一个特定应用的。docker system prune
是用来给整个系统做大扫除的。
它们的目的完全不同,绝对不能混用。
docker prune 遇到的常见问题
为啥空间没被释放?
这是最常见的问题:“我运行了 prune
,但 df -h
显示磁盘空间没啥变化!”
最常见的原因是:
- Docker 守护进程仍在占用文件句柄: 有时,即使资源被删除了,Docker 守护进程可能还没有释放对文件的锁定。重启 Docker 守护进程通常能解决问题 (
systemctl restart docker
)。 - 真正占空间的东西没被删:
prune
默认不删卷和有标签的镜像。也许你真正需要的是docker system prune -a --volumes
。 - “看不见的"被占空间: 真正占用大量空间的是 Docker 的
overlay2
目录(通常在/var/lib/docker/overlay2
)。即使你删除了容器和镜像,一些底层的存储层可能因为某些原因没有被正确清理。重启 Docker 或在极端情况下清理整个 Docker 目录(非常危险!)可能是最后的手段。
某个资源为什么删不掉?
你确定一个容器已经停止,一个镜像没被使用,但 prune
就是不删它。
原因可能是:
- 还有东西在用它: 你可能忽略了一个正在使用该资源的容器(即使是已停止的容器也会占用镜像和卷)。仔细用
docker ps -a
和docker volume inspect
检查。 - 它是另一个镜像的基础: 一个镜像可能是另一个镜像的基础层(父镜像)。在这种情况下,除非你把所有依赖它的子镜像都删掉,否则它不会被删除。
清理命令卡住或者特别慢
当你有成千上万个 Docker 对象时,docker system prune
可能会运行得非常慢,甚至卡住。这是因为它需要把所有东西的关系都捋一遍,计算出哪些是可以安全删除的,这是一个非常耗费 I/O 的操作。
解决方法:
- 分步清理: 不要用
system prune
,而是分别运行container prune
,image prune
等。 - 使用过滤器:
until
过滤器可以显著减少需要检查的对象数量,从而加快速度。 - 定期清理: 不要等到系统里有几万个对象了才想起来清理。养成定期清理的习惯,每次只处理少量对象,速度会快很多。
总结
docker prune
是一个必不可少的工具,但它也是一把双刃剑。Docker 天生就"保守"的设计,决定了系统维护的责任最终落在了用户自己身上。
核心要点
- 默认保平安:
prune
命令的默认行为是相对安全的,它不会动你的数据(卷)和有用的镜像。 - 危险参数要警惕:
--all
和--volumes
会大大增加清理的力度,也带来了数据丢失的风险。 - 过滤器是你的安全网: 学会使用
label
和until
过滤器,可以让你在享受自动化便利的同时,保证核心资源的安全。 - 环境决定策略: 开发环境可以大刀阔斧,生产环境必须谨小慎微。
- 清理是一项必须要做的工作,而不是可选项: 忽略 Docker 的清理工作,最终会导致磁盘耗尽、性能下降和管理混乱。
通用法则是:在不确定的时候,不要删。在删除之前,先检查。对于重要数据,永远要备份。