镜像仓库里的镜像越积越多,安全清理的核心答案只有六个字:先标记,后清理。直接删文件或盲目执行docker rmi都会留下悬空层或误删仍在使用的版本,要兼顾磁盘空间和业务稳定,必须建立一套从标记到回收的完整流程。
镜像越积越多,安全清理先搞清楚哪些不能动
很多运维朋友处理镜像仓库的磁盘告警时,第一反应是登上服务器执行docker system prune -a,这个命令确实能清理悬空镜像,但它不区分镜像的业务归属,在生产环境里,刚构建完还没来得及推送到仓库的本地镜像、正在调试的中间版本、以及流水线里回滚用的上一个稳定版,都可能被它当成垃圾清掉。
镜像清理的难点不在“怎么删”,而在“怎么判断哪些能删”,行业共识认为,安全的清理策略必须绑定镜像的元数据和使用场景,而不是只看仓库里的列表大小。
三种镜像类型,风险等级完全不同
按使用频率和不可替代性,仓库里的镜像可以粗暴分成三类:
- 活跃镜像:最近一个季度内被拉取或更新的版本,通常是生产环境正在跑的,这类镜像绝对不动。
- 沉睡镜像:超过90天没有被拉取,但属于某个历史发布版本,可能需要用于回滚,这类镜像先标记,不删除。
- 僵尸镜像:构建失败产生的半成品、被新版本替换掉的旧
latest标签指向的镜像、以及没有任何标签和容器引用的悬空镜像,这些才是真正的清理目标。
判断依据很简单:看镜像的Created时间和Last Pulled时间。 大部分私有仓库(Harbor、JFrog Artifactory)的UI界面或API接口都提供了这两个字段,如果某个镜像创建了半年,最近一次拉取是三个月前,而且没有关联任何运行中的容器,基本可以归入清理候选列表。
清理前必须完成的四项安全检查
动手清镜像之前,建议花十分钟做一个快速审计,能避免绝大多数的误删事故。
- 确认各命名空间的保留策略,开发环境的镜像可以保留最近10个版本,生产环境保留最近5个,
latest标签永远指向最新稳定版,没有这个规则,清理就是个凭感觉的活。 - 检查流水线是否有外部依赖,有些CI/CD流程会直接通过镜像的SHA256摘要来拉取版本,而不是用标签,这就意味着即便你保留了两个
标签,如果流水线配置里写死了旧SHA,镜像一旦删除,下一次构建会直接失败。latest
- 扫描镜像中的敏感信息,这听起来和清理无关,但相当一部分“不敢删”的镜像里藏着旧环境变量或密钥文件,删除前用Trivy或Clair扫一遍,把发现的密钥轮换掉,才能放心归档,否则清理仓库的同时可能会把唯一的凭证副本也弄丢。
- 验证备份的可用性,仓库的备份不能只停留在“每天凌晨打包
/var/lib/registry”,得实际从备份里恢复一个镜像,确认能正常推送和拉取,镜像文件是二进制数据,磁盘能启动不代表备份文件完整。
docker镜像清理有哪些风险点和操作规范
解决了“哪些能动”的判断问题,接下来看“怎么动”的具体操作,docker镜像清理的风险点主要集中在镜像层引用关系上,镜像不是单个文件,而是由多层只读层叠加而成的,如果A镜像和B镜像共享底层,清理B时误删了共享层,A镜像也会跟着无法启动。
安全执行清理的四个步骤
第一步:给镜像打上清理标记
登录到镜像仓库的管理后台,找到目标镜像,修改其标签或描述信息,比如在Harbor里,可以创建一个名为to-be-cleaned的标签并标记到候选镜像上,这一步相当于给镜像办了“准删证”,留出一个观察期,建议观察期不少于72小时,用于确认没有线上服务在调用这些版本。
第二步:核对运行中容器的引用关系
登上有业务的服务器,执行以下命令,找出当前所有容器正在使用的镜像ID:
docker ps --format "table {{.Image}}" | sort -u
然后把输出的镜像ID或镜像名与仓库里标记为to-be-cleaned的列表做比对,出现交集的一律从清理名单里移除。
第三步:按顺序执行清理动作
对于已经确认无引用的镜像,按照先停用后删除的顺序操作:
- 先将仓库里的镜像标签停用(disable),不让新的拉取请求命中它;
- 之后执行仓库的垃圾回收机制,Harbor仓库的删除操作不会立刻释放磁盘空间,必须在Harbor的管理界面点击“垃圾回收”(Garbage Collection),或者在服务器上执行
docker run --rm --name gc --volumes-from registry /bin/rm -rf /etc/docker/registry这类命令,镜像层的物理文件才会被真正清除。
第四步:在本地节点同步清理
仓库清完了,跑代码的服务器上还存着那一份镜像缓存,如果不处理,服务器的磁盘依然会被占满,连接需要清理的服务器,执行:
docker image prune -a --filter "until=168h" --filter "label!=keep"
这条命令会清理7天前创建的、且没有keep标签的镜像,通过label!=keep这个过滤条件,给必须保留的镜像加一层保险。
镜像仓库清理工具的对比选择
针对不同规模仓库,工具选型很关键,以下按适用场景做一个横向对比:
| 工具/方法 | 适用场景 | 清理方式 | 安全等级 |
|---|---|---|---|
docker image prune |
单机开发环境 | 命令行交互式清理悬空镜像 | |
| Harbor 标签保留规则 | 中等规模私有仓库 | 基于规则的定时清理 | |
| JFrog Artifactory 清理策略 | 企业级制品库 | 结合生命周期管理自动归档 | |
| Nexus Repository 资产清理 | 混合制品管理 | 手动或脚本触发REST API清理 | |
| 自研脚本结合API调用 | 需要完全自定义策略 | 按业务字段(如Git提交号)精确筛选 |
多数场景下,Harbor的标签保留规则加上定时垃圾回收,基本能覆盖日常需求,如果还在用裸的docker registry容器,就得接受一个现实:它本身没有镜像删除API,需要直接操作存储目录,这时的风险等级会直线上升,不建议在生成环境这么做。
清理过程遇到两个高频故障怎么处理
镜像删除了但磁盘空间没变化
这是最让人头疼的问题,堆在/var/lib/docker目录下的空间没有释放,大概率是容器日志文件或挂载的卷占用了空间,而非镜像本身,清理后建议执行df -h确认释放结果,如果空间确实没回来,检查一下是否有容器处于退出状态但未删除,执行docker container prune再清一轮。
删除后恢复时提示镜像不存在
回滚操作本来用的是v1.2.0这个标签,但清理时连标签带镜像一起删了,导致回滚时拉取不到镜像,这种情况在操作上不太常见,但一旦发生影响面较大。规范做法是裸registry仓库中为任何镜像新建副本之前,不主动删除任何打标签的镜像
,使用Harbor等专业仓库时,建议开启“不可变镜像”功能,把生产环境的已验证版本锁定起来。
镜像仓库清理误删之后能否恢复
如果操作不慎删除了还在使用的镜像,恢复途径极其有限,镜像的层文件本身没有被覆盖,但标签指向已经没了,恢复的关键取决于垃圾回收是否已经被触发,如果在执行GC之前发现,可以尝试重新推送一份相同的镜像,但如果唯一的生成文件(Dockerfile或构建上下文)已经不存在,情况会变得很棘手。
基于这个风险,可靠的清理策略是“始于仓库,终于备份”,平时多镜像仓库架构下,可以考虑将生产环境的镜像定期同步到异地只读仓库作为灾备,清理前花两分钟确认该镜像在灾备仓库中有完整副本,比事后花两小时抢救要划算得多。
自动化清理是另一个值得投入的方向,借助Harbor的Webhook功能,可以在镜像推送到仓库时自动记录版本数,并触发清理脚本,这样一来,既省去了每次手动梳理标签的麻烦,也避免了人为判断失误,让系统自动生成“该清理什么”和“必须保留什么”的清单。
镜像仓库清理常见问题解答
Q:镜像仓库清理为什么不能直接在服务器上删除文件?
A:镜像仓库的存储目录里的文件按层(Layer)组织,层的引用计数分散在多个镜像的Manifest中,直接删除文件会破坏其他镜像的完整性,且不会更新仓库的索引数据库,安全的做法是使用仓库自带的API或界面的删除功能,然后执行垃圾回收来清理物理文件。
Q:清理镜像时提示“image is being used by container”如何处理?
A:这是守护进程的引用保护机制生效,意味着有容器正使用该镜像,先执行docker ps -a找到对应容器,确认容器是否可以停止,若容器有日志需要留存,先docker cp导出日志再删除容器,之后即可通过docker image rm移除镜像,若容器属于某个服务,需先停止服务避免自动重建容器。
Q:Harbor仓库执行垃圾回收后,标签仍显示但拉取镜像时报错?
A:这是标签(Tag)和 Manifest 之间的索引失联,通常因为清理过程中存在并发推送导致GC误判,可以尝试在Harbor中重新创建同名的标签指向正确的Manifest,或者直接推送一次相同内容的镜像来重建关联,若操作无效,需检查GC日志是否把仍被引用的层当成了孤儿层。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/639134.html





