拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Docker镜像删除与清理:从docker rmi到空间回收实战指南

用 Docker 时间一长最容易出事儿的还真不是容器跑不起来而是你拉了一堆镜像磁盘分区直接变红。尤其是做开发的朋友Redis、MySQL、GitLab、各种中间件的镜像随手就docker pull再加上每次改 Dockerfile 重新 build 产生的旧镜像层/var/lib/docker目录轻轻松松就能吃掉几十个 G。这篇文章专门聊镜像删除和清理这档子事从最基础的docker rmi讲到整体空间回收一步步说清楚怎么把磁盘救回来也把那些容易踩的坑一并排掉。适合被镜像占满磁盘困扰的开发者、运维和自学者内容不假设你有多少 Docker 基础照着敲就行。1. 删除镜像之前先把依赖关系理清楚1.1 镜像分层与引用关系很多人上来就是一通docker rmi删不掉就开始怪命令不对其实核心问题出在没理解 Docker 对象之间的引用关系。镜像本身是由多层只读层叠加出来的模板容器是基于镜像创建出来的运行实例。用个生活化比喻镜像是“安装包”容器是“装好并打开的软件”。你想删安装包前提是这个软件没在运行、也没保留在桌面上同理如果容器还活着镜像就被容器引用着Docker 不会让你轻易删掉。所以删除镜像之前必须先搞清楚哪些容器还在占用我想要的镜像哪些镜像已经彻底没用了如果带着引用关系硬删要么报错给你看要么用-f强删但强删之后容器那边会留下一个“没有底层模板”的悬空状态排查问题时照样头疼。1.2 先用 docker system df 摸底不论是想删单个镜像还是想一次性清理磁盘第一步都建议先跑一下docker system df它会像磁盘分析工具一样把 Docker 各个对象占用的空间一次性列出来。我一般会先执行这个命令再决定下一步怎么清docker system df输出大概是这样的TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 12 4 28.3GB 19.8GB (69%) Containers 6 2 1.2GB 1.1GB (91%) Local Volumes 8 3 4.5GB 3.0GB (66%) Build Cache 35 0 6.7GB 6.7GB (100%)RECLAIMABLE 这一列非常关键它表示当前状态下可以释放的空间。比如 Build Cache 的 RECLAIMABLE 是 100%意味着只要缓存一清6.7 G 全部回来。Images 回收率 69%说明有些镜像有 tag 或被容器占用暂时不能全清但悬空镜像部分是可以回收的。看到这张表之后再决定是删单个镜像还是做整体清理就不会像无头苍蝇一样乱灌命令了。1.3 哪些镜像能删、哪些不能删根据我的经验可以把镜像分成三种状态能直接删、需要先处理再删、暂时不要删。能直接删没有任何容器引用、也没有被其他镜像依赖的镜像尤其是悬空镜像。需要先处理再删正在运行的容器或已停止但未删除的容器所引用的镜像先删容器或docker rm再删镜像。暂时不要删被多个容器同时使用的镜像、带有明确版本 tag 且业务还在用的镜像删掉可能引发下次部署直接失败。有多个 tag 的情况也要注意。同一个镜像 ID 可能同时挂了mysql:8.0和mysql:latest两个标签删除其中一个标签时镜像文件本身不会真的没了只有最后一个标签也被删掉时镜像才可能变成悬空状态随后才能被清理。这个细节很多人第一次遇到会误以为“删了个寂寞”其实底层逻辑就是这样。2. 掌握 rmi镜像删除的基础操作2.1 命令基本写法和参数删除镜像最核心的命令就是docker rmi也可以写成长格式docker image rm两者完全等价。最常用的写法有几种# 按名称和标签删除 docker rmi mysql:8.0 # 按镜像 ID 删除 docker rmi 0d64f46acfd1 # 一次删除多个镜像 docker rmi nginx:1.25 redis:7.0 mysql:8.0 # 强制删除 docker rmi -f mysql:8.0其中 ID 不一定要写完整只要你输入的短 ID 能唯一匹配一个镜像就行。比如完整 ID 是0d64f46acfd1a3860b64d8d5d1f7f5c8e6f06b1d通常写前四位到前六位就够了Docker 会自己解析。删除成功后终端会输出被删掉的镜像层的 ID 列表。如果你看到Untagged: mysql:8.0说明这个 tag 被删了如果看到Deleted: sha256:...说明镜像层数据真的被清理了。只出现 Untagged 而没出现 Deleted就是前面说的多 tag 情况镜像本体还躺在磁盘上。2.2 按标签删、按 ID 删有什么区别我见过不少新手问我明明删了 tag为什么docker images里还能看到这个镜像这里要专门说一下。如果两个 tag 指向同一个镜像 ID你执行docker rmi mysql:latest只是把 latest 这个标签从镜像记录里摘掉镜像层还是被mysql:8.0引用着所以docker images里依然能看见mysql:8.0这不算把镜像删掉。如果你直接用镜像 ID 删除并且这个 ID 对应多个 tagDocker 会拒绝执行提示你“image is referenced in multiple repositories”需要先删 tag 或使用-f。所以实际操作中我推荐优先用“仓库名 标签”来删这样你知道自己到底删了什么不会误伤共用 ID 的其他 tag。2.3 force 强制删除到底该不该用docker rmi -f是个双刃剑。它的作用是在镜像被容器引用时强行解除引用并删除镜像数据但容器本身并不会被删除只是它依赖的镜像数据没了后续想启动这个容器基本没戏。我自己的原则是能不用-f就不用。如果你只是想腾空间先把容器处理干净再正常删镜像花不了几秒钟。什么情况下可以破例呢比如你已经确认所有容器都不重要明天就要全部重建这时候docker rmi -f $(docker images -aq)能快速把所有镜像一次性干掉省去先删容器的步骤。但前提是“你确认没问题”一旦误删想恢复只能重新 pull 或 build。2.4 新手最容易搞混docker rm 和 docker rmi 的区别这个问题太常见了必须单独拎出来讲。docker rm删的是容器docker rmi删的是镜像。字母上就差一个 i实际作用完全不是一回事。可以这样记先有镜像才有容器所以清理时顺序是“先 rm 后 rmi”。你不可能在容器还存在的情况下把镜像删掉。另外docker ps默认只显示运行中的容器已停止的容器要用docker ps -a查看很多人以为停止了就等于删除了其实停止的容器仍然占磁盘空间也仍然引用着镜像这也是“怎么删镜像都删不掉”的一大原因。3. 系统级清理一次搞定悬空镜像、无用容器和缓存3.1 什么是悬空镜像image prune 怎么用悬空镜像英文叫 dangling image指的是没有 tag、也没有被任何容器引用的镜像层。它通常是这么产生的你写了一版 Dockerfile构建出镜像my-app:v1然后改了代码又构建了一次my-app:v1新镜像会覆盖旧镜像的 tag旧的镜像就失去标签变成none:none的悬空镜像。单独清理悬空镜像用docker image prune -f加-f是跳过确认提示。如果想把所有“没有被容器使用的镜像”全部清理掉无论是悬空还是有 tag 的可以加-adocker image prune -a -f注意这个-a很危险。它会删除所有没有被任何容器引用的镜像包括你刚 pull 下来准备之后用的centos:7、ubuntu:22.04等。如果只是想腾空间而且不想下次部署时重新拉我建议平时只清理悬空镜像就足够了。想看到底有哪些悬空镜像可以先执行docker images -f danglingtrue确认一下再清心里更有数。3.2 system prune 一键清理如果你既想删悬空镜像又想清理停止的容器、无用的网络和构建缓存一条命令就能搞定docker system prune -f这是日常最推荐的清理命令。它不会删带有 tag 且没有被容器使用的镜像也不会删数据卷相对温和。如果磁盘已经红到发紫想做得更彻底可以用docker system prune -a --volumes -f这个组合拳的效果是删除所有停止的容器、所有未被容器使用的网络、所有悬空镜像、所有未被容器使用的已标记镜像以及所有未被容器使用的匿名数据卷。非常有效但副作用也大尤其是--volumes会把没有容器引用的匿名卷里的数据一并清掉。如果你不太确定有没有重要数据别加这个参数。建议第一次执行时不要加-f让 Docker 把准备删掉的资源列出来给你确认一遍看清楚哪些会被删再敲 y 回车。虽然多一步但能防手滑。3.3 构建缓存才是隐形杀手builder prune 的用法很多人清完了镜像发现磁盘空间还是没怎么降这时候八成是构建缓存Build Cache在作怪。每执行一次docker buildDocker 都会把中间层和缓存写入/var/lib/docker日积月累缓存比镜像本身还大一点也不奇怪。单独清理构建缓存用# 清理不再使用的构建缓存 docker builder prune -f # 清空全部构建缓存 docker builder prune -a -f # 只清理指定时间之前的缓存比如 24 小时前 docker builder prune -f --filter until24h清缓存的影响是下次重新构建同一个项目时无法复用之前的层缓存构建时间会变长。所以如果你之后还要频繁 build时间比磁盘空间更值钱那就别全清用until24h或until48h只清旧缓存更合理。还有一个相关小技巧临时想要构建一个新镜像又不想污染缓存可以docker build --no-cache但它不是清理方案只是跳过缓存构建别和docker builder prune搞混了。3.4 数据卷别乱删镜像、容器、构建缓存都清理完了还剩下 Local Volumes 可能占着好几 G。数据卷是用来持久化容器数据的比如 MySQL 的数据库文件、GitLab 的仓库文件都会放在卷里。清理数据卷用docker volume prune -f但这句话只会删除“没有被任何容器使用的匿名卷”。带名字的数据卷即使容器删了卷也会保留下来避免数据丢失。如果你确认某些命名卷也没用了直接用docker volume rm volume-name删掉即可。我在实际项目里见过不少把数据库文件放匿名卷里然后docker rm容器时没加-v导致卷残留、磁盘越来越满的情况。所以删容器时如果能确定不需要保留数据可以直接用docker rm -v container-id把容器和它关联的匿名卷一起删掉省得后面还要单独清理。但带上-v之前一定想清楚卷里的数据不会再回来。4. 实战复盘从“磁盘红了”到“空间释放”的一次完整排查4.1 场景描述磁盘告警后的处理流程有一次我在一台测试服务器上发现磁盘使用率到了 95%监控直接报警。这台机器上装了 Docker跑着好几个 API 服务还经常构建新镜像。登录服务器后我习惯性地先看整体磁盘情况df -h输出里能看到/var/lib/docker所在的分区已经用了 80 多 G再看一眼docker system dfImages 占了 34 GBuild Cache 占了 22 GContainers 占了 4 GLocal Volumes 占了 9 G可回收空间总计超过 50 G。问题很明显了这是一台典型的“镜像和缓存长期不清理”的机器。4.2 用命令定位空间大户如果还想进一步定位是哪个镜像占空间可以用docker images --format table {{.Repository}}\t{{.Tag}}\t{{.Size}}或者直接列出所有镜像按大小排序用docker image ls配合sort。我会特别关注那些带none标签的悬空镜像以及几个动辄 2 G 以上的大镜像。在排查时不要只看 Images 和 Containers还要留意日志文件。容器日志默认写在/var/lib/docker/containers/container-id/-json.log如果某个服务疯狂打日志这个文件能轻松涨到几个 G。想确认哪个容器日志最大可以这样for id in $(docker ps -aq); do log_path$(docker inspect --format{{.LogPath}} $id) size$(du -sh $log_path 2/dev/null | awk {print $1}) echo $id $size done不过这是题外话本次主要解决镜像和缓存日志问题就不展开了。4.3 逐步执行清理并对比前后空间变化我的清理顺序是先清缓存再清容器再清悬空镜像最后清卷尽量不影响正在运行的服务。第一步先清理已经停止的容器docker container prune -f系统提示删除了 4 个停止容器收回了大约 1.8 G 空间。注意正在运行的容器不会被删除所以这一步对线上服务没有影响。第二步清构建缓存docker builder prune -f --filter until48h这一步释放了 15 G 空间效果最明显。如果你平时构建很频繁这里绝对是空间大头。第三步清理悬空镜像docker image prune -f删除了 6 个none镜像释放了 4.3 G。我没有用-a因为本地还放着一个 CentOS 7 镜像和一个 MySQL 8.0 镜像虽然当前没被容器用但过两天部署还会用到没必要重新拉。第四步再跑一次docker system df确认结果。总占用从 69 G 降到了 47 G 左右可回收空间还有 12 G主要是暂时不能删的镜像和遗留的命名数据卷。磁盘使用率从 95% 降到了 82%服务恢复健康水位。4.4 一套适合定期执行的镜像清理策略手动清理只能解一时燃眉之急想长期不踩雷我建议把这套操作固化成脚本。比如在服务器上放一个docker-clean.sh#!/bin/bash docker image prune -f docker container prune -f docker builder prune -f --filter until24h docker system df /var/log/docker-clean.log然后通过 crontab 每周六凌晨跑一次0 2 * * 6 /usr/local/bin/docker-clean.sh /var/log/docker-clean.log 21这个策略的核心思路是绝不在自动脚本里加-a和--volumes只做安全清理。这一步能清掉绝大多数可回收空间又不会误删你辛辛苦苦拉下来的业务镜像。如果你有特殊需求比如某些镜像必须长期保留可以给镜像打上特定标签然后在脚本里加 filter比如--filter labelkeep这样带 keep 标签的镜像永远不会被清理。5. 常见问题速查与避坑笔记5.1 明明删了镜像磁盘空间却没有释放这个问题出现频率极高。删了镜像但空间没释放原因无非以下几种容器还在引用镜像删除的只是镜像的 tag底层数据没被真正清理。停止的容器没有被删除容器可写层和日志文件还占着空间。数据卷没删卷数据独立于镜像生命周期。构建缓存占了大头而你只删了镜像没清 Build Cache。你磁盘上的空间本来就有一部分来自日志文件或其他目录不全是镜像。排查顺序建议先docker ps -a看有没有残留容器再docker system df看缓存和卷的占用最后看日志文件。绝大多数“删了没释放”的问题都能在这几步里找到答案。5.2 删除时报 image is being used by container这个报错的意思很直白有容器正在使用这个镜像哪怕容器是停止状态也算。你可以先找到引用它的容器docker ps -a --filter ancestormysql:8.0找到后如果这个容器确定没用直接删掉docker rm container-id然后再删除镜像。如果容器特别多可以一次性把所有停止容器都删掉docker container prune -f之后再docker rmi就不会报错了。强行docker rmi -f虽然能绕过这个限制但不建议成为习惯因为后续你会面临一堆无法启动的容器残留。5.3 多个 tag 导致删不掉前面提到过同一个镜像 ID 挂了多个 tag删的时候会提示 referenced in multiple repositories。处理方法是先逐个删 tagdocker rmi repo1:tag1 repo1:tag2 ...等到只剩最后一个 tag 的时候再删一次镜像层才会真正被释放。如果不想这么麻烦而且确认所有的 tag 都不要了可以直接强制删镜像 IDdocker rmi -f image-id然后docker images -a确认是否还有残留的none镜像再用docker image prune -f清理掉。5.4 用 :latest 标签带来的麻烦我见过很多项目为了省事拉镜像、启动容器全用:latest。时间一长latest 被反复覆盖旧镜像全部变成悬空镜像占用大量空间。而且:latest不代表“稳定版本”谁也无法保证它下个版本不会引入破坏性的变化。如果磁盘紧张还容易造成明明清理过、过几天又满了的循环。所以个人建议生产环境或平时练习都尽量把 tag 固定到具体版本上比如nginx:1.25、mysql:8.0.36。这样至少在docker images里能一眼看出版本也便于清理和回滚。构建自己项目镜像时也尽量打上版本号别只打个 latest。我自己的习惯是隔段时间就跑一下docker system df看一眼可回收的空间发现超过 10 G 就顺手清一轮。清理时只动悬空镜像、停止容器和旧构建缓存绝不对命名的数据卷和正在使用的版本镜像下手。这样操作风险可控而且每次基本都能把磁盘水位压回健康区间。Docker 的空间管理本质上不是高深技术而是肯不肯养成定期看一眼的习惯。把这几个命令记熟你的磁盘就不会再动不动就报警了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门