ECS 服务器 Docker 镜像堆积清理实战:安全释放 4.6GB
更新: 10/10/2026字数: 0 字 时长: 0 分钟
前言
服务器用久了,docker images 列表越来越长——同一个项目因为 CI 每次提交都打一个新 tag 推送,导致本地堆积了几十个历史镜像,磁盘告急。直接 docker rmi 又怕删错正在用的镜像。本文记录一次完整的排查与安全清理过程。
问题背景
先看清理前的镜像列表(已节选):
使用 docker images 命令:
[root@iZuf68x0y81jmfv00sbveuZ conf.d]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
crpi-xxx.com/codeview/web 68a3349d... f4da58044ab7 28 min 49.8MB
crpi-xxx.com/codeview/server 68a3349d... 0948d11814ad 28 min 330MB
crpi-xxx.com/codeview/web bdfaa849... f151846687aa 50 min 49.8MB
crpi-xxx.com/codeview/server bdfaa849... 19cf3a0030d0 51 min 330MB
crpi-xxx.com/gouxinjie/deepxinjie-web 561dd0e8... feae061f2f56 2 months 52.8MB
crpi-xxx.com/gouxinjie/deepxinjie-web c3f76d0a... feae061f2f56 2 months 52.8MB
... (共几十个历史 hash tag)如图: 
特征很明显:
codeview/web、codeview/server有大量以 git commit hash 作为 tag 的历史版本;deepxinjie-web、deepxinjie-api各有十几个旧版本;- 还混着
redis:7-alpine、nginx:1.27-alpine等可能已不用的公共镜像。
核心认知:删除镜像的唯一风险
删除镜像的唯一风险就是:删掉了正在被容器使用的镜像(包括已停止、但之后还要重启的容器)。
只要镜像还在某个容器的 IMAGE 列里,就不能删。除此之外的历史镜像,尤其是「已推送到镜像仓库」的 hash tag,本地删除是低风险的——需要时还能 docker pull 重新拉回来。
第一步:摸清容器与镜像的对应关系
先看清楚「谁在用哪个镜像」,这是后续一切操作的前提:
docker ps # 正在运行的容器
docker ps -a # 所有容器(含已停止的)
# 用表格一次看清 容器名 -> 镜像 -> 状态
docker ps -a --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'输出示例:
NAMES IMAGE STATUS
codeview-web crpi-xxx.com/codeview/web:68a3349d... Up 32 minutes
codeview-server crpi-xxx.com/codeview/server:68a3349d... Up 32 minutes
mylab crpi-xxx.com/gouxinjie/mylab:latest Up About an hour
deepxinjie-web crpi-xxx.com/gouxinjie/deepxinjie-web:c3f76d0a... Up 2 months
deepxinjie-api crpi-xxx.com/gouxinjie/deepxinjie-api:c3f76d0a... Up 2 months
deepxinjie-db mysql:8.0 Up 2 months
weather-frontend weather-frontend:latest Up 2 months
weather-backend weather-backend:latest Up 2 months关键判断:本案例中所有容器都是 Up 状态,没有「已停止但占着旧镜像」的容器。这就意味着可以直接用自动清理命令,它只会删「没有被任何容器引用」的镜像。
第二步:清理悬空镜像(零风险)
悬空镜像是 <none>:<none>,没有任何 tag 引用,可放心删除:
docker image prune -f[root@iZuf68x0y81jmfv00sbveuZ conf.d]# docker image prune -f
Total reclaimed space: 0B本案例的列表里其实没有
<none>:<none>,所以这步删不了什么(显示:Total reclaimed space: 0B),真正的对象是历史 commit 版本。但它作为日常清理习惯仍然值得保留。
第三步:清理未使用的历史镜像(关键一步)
由于确认了「没有停止容器占坑」,直接用 -a 参数删除所有未被容器引用的镜像:
docker image prune -a -f这一条命令会自动识别并保留正在使用的 9 个镜像,删除其余全部历史版本。执行结果(节选):
Deleted Images:
deleted: sha256:134b203d85e8...
deleted: sha256:f151846687aa...
deleted: sha256:99b1b034863b...
deleted: sha256:10ca95f65171...
deleted: sha256:6769dc3a703c...
...
Total reclaimed space: 1.246GB如图: 
为什么只回收了 1.246GB?
之前按 docker images 里的 SIZE 列估算约 3.8GB,实际只回收 1.246GB。原因是 Docker 镜像层大量共享:
SIZE列是「引用后的逻辑大小」之和;- 不同镜像共享同一个基础层(如 alpine/node 基础层);
- 真正落盘的唯一层远少于
SIZE之和。
这是正常现象,不代表没删干净。
第四步:清理构建缓存
镜像删完后,再清一层构建缓存:
docker builder prune -f输出中列出所有可回收缓存,最后一行 Total: 3.4GB 即本次可回收的构建缓存总量。
如图:

如果还想更彻底(连当前镜像的构建层缓存也一起删),可加 -a:
docker builder prune -a -f注意代价:
-a会删除所有构建缓存,下次重新docker build项目会变慢(层缓存失效),但对正在运行的容器没有任何影响。
效果总结
| 操作 | 回收空间 | 说明 |
|---|---|---|
docker image prune -a -f | 1.246GB | 历史镜像(含共享层) |
docker builder prune -f | 约 3.4GB | 构建缓存 |
两项合计约 4.6GB,且 9 个运行中的容器全程不受影响。
最终确认可用:
docker system df避坑指南
绝对不要碰
docker volume prune服务器上跑着mysql:8.0,数据库数据几乎肯定存在数据卷里,盲目清卷可能直接清掉数据库。确认有备份前不要动卷。先
docker ps -a再删 有「已停止容器」时,docker image prune -a不会删停止容器引用的镜像,但手动docker rmi可能会删掉。所以手动删除前务必核对。-a与构建缓存docker builder prune不带-a只删「悬空」缓存,带-a才删「所有未使用」缓存,按需选择。
根治方案:CI 侧限制本地镜像堆积
历史镜像的根源是 CI 每次提交都打 hash tag 推送,本地从不清理。可以在 CI 推送后加一步「只保留最近 N 个版本」:
#!/usr/bin/env bash
# 推送后清理本地旧镜像:每个仓库只保留最近 3 个版本
set -e
REPO="crpi-xxx.com/gouxinjie/deepxinjie-web"
KEEP=3
# 按创建时间排序,跳过前 KEEP 个,删除更早的镜像
docker images "$REPO" --format '{{.CreatedAt}}\t{{.ID}}' \
| sort -r \
| tail -n +$((KEEP + 1)) \
| awk '{print $2}' \
| xargs -r docker rmi -f也可以配合 cron 或部署脚本定期执行 docker image prune -f,避免镜像再次堆积。