高效清理未使用的Docker容器与镜像:Shell脚本实战指南
目录导读
-
为何需要清理未使用的容器与镜像

- 磁盘空间占用与性能影响
- 安全与合规性考量
-
理解Docker资源状态:容器与镜像的关系
- 运行中、已停止、悬空镜像的区别
- 常用命令回顾:
docker ps -a与docker images
-
Shell脚本清理策略设计
- 安全过滤:避免误删正在使用的资源
- 按时间、标签、大小分层清理
-
实战Shell脚本:从基础到高级
- 清理所有已停止的容器
- 删除未被任何容器引用的悬空镜像(dangling images)
- 按日期清理老旧镜像(保留最近30天)
-
自动化与定时任务集成
- cron作业设置技巧
- 日志记录与异常通知
-
常见问题解答(FAQ)
- Q1:如何只清理未被任何容器使用的镜像?
- Q2:清理后镜像可以恢复吗?
- Q3:大规模清理时如何防止脚本超时?
-
最佳实践与安全建议
- 提前备份关键镜像
- 使用
--filter参数降低误删风险
为何需要清理未使用的容器与镜像
Docker在开发与生产环境中广泛使用,但长期运行后,系统会积累大量已退出的容器、未引用的镜像以及悬空镜像(<none>:<none>),这些资源不会自动删除,可能导致:
- 磁盘空间枯竭:典型的悬空镜像可能占用数GB,尤其在频繁构建的应用中。
- 性能下降:
docker ps -a和docker images命令需解析大量元数据,影响CI/CD流水线速度。 - 安全风险:过时的镜像可能包含已知漏洞,未清理的容器可能残留敏感数据。
核心问题:手动执行docker system prune -a虽方便,但会删除所有未被使用的资源(包括运行中的卷和网络),不适用于精细化场景,编写自定义Shell脚本成为精准清理的首选方案。
理解Docker资源状态:容器与镜像的关系
在编写脚本前,需掌握三个关键概念:
- 已停止的容器:
docker ps -a中STATUS为Exited或Created的容器,它们仍占用磁盘(容器层数据),但不再消耗CPU/内存。 - 悬空镜像:
docker images中REPOSITORY和TAG均为<none>的镜像,这些是构建过程中残留的中间层。 - 未使用的镜像:未被任何容器(包括停止的容器)引用的镜像。
docker image ls --filter dangling=false可列出这类镜像。
安全清理原则:脚本应保留与运行中或已停止容器关联的镜像,只删除“孤儿”资源。
Shell脚本清理策略设计
一个健壮的清理脚本应包含以下模块:
- 安全过滤:使用
docker ps -a -q获取所有容器ID,避免误删被容器引用的镜像。 - 分层清理:
- 第一层:删除所有已停止的容器(回收容器层空间)。
- 第二层:删除悬空镜像(立即释放空间)。
- 第三层:按创建时间删除未被引用的老旧镜像(例如保留最近30天)。
- 日志记录:将清理结果输出到文件,便于审计。
示例伪代码:
删除所有状态为Exited的容器
删除所有REPOSITORY和TAG为<none>的镜像
对于每个镜像,检查是否被任何容器引用,若未被引用且创建时间早于30天前,则删除
实战Shell脚本:从基础到高级
以下是一个经过测试的Shell脚本(docker-cleanup.sh),完整覆盖上述策略:
#!/bin/bash
# Docker资源清理脚本 v2.1
# 功能:清理未使用的容器、悬空镜像及30天前的未引用镜像
# ---------- 配置区 ----------
KEEP_DAYS=30 # 保留镜像的天数
LOG_FILE="/var/log/docker-cleanup.log"
DRY_RUN=false # 设为true进行模拟运行
# ---------- 容器清理 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始清理已停止的容器..." | tee -a $LOG_FILE
STOPPED_CONTAINERS=$(docker ps -a -q -f "status=exited" -f "status=created")
if [ -n "$STOPPED_CONTAINERS" ]; then
if [ "$DRY_RUN" = false ]; then
docker rm $STOPPED_CONTAINERS 2>&1 | tee -a $LOG_FILE
else
echo "模拟删除容器:$STOPPED_CONTAINERS" | tee -a $LOG_FILE
fi
else
echo "无已停止的容器待清理。" | tee -a $LOG_FILE
fi
# ---------- 悬空镜像清理 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始清理悬空镜像..." | tee -a $LOG_FILE
DANGLING_IMAGES=$(docker images -f "dangling=true" -q)
if [ -n "$DANGLING_IMAGES" ]; then
if [ "$DRY_RUN" = false ]; then
docker rmi $DANGLING_IMAGES 2>&1 | tee -a $LOG_FILE
else
echo "模拟删除悬空镜像:$DANGLING_IMAGES" | tee -a $LOG_FILE
fi
else
echo "无悬空镜像待清理。" | tee -a $LOG_FILE
fi
# ---------- 按时间清理未引用镜像 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始清理30天前的未引用镜像..." | tee -a $LOG_FILE
# 获取所有镜像及其创建时间(时间戳格式)
TOTAL_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}|{{.ID}}|{{.CreatedAt}}")
# 获取所有正在使用(被容器引用)的镜像ID
USED_IMAGES=$(docker ps -a -q | xargs -r docker inspect --format '{{.Image}}' | sort -u)
# 遍历每个镜像,判断是否被使用且创建时间超过KEEP_DAYS
while IFS='|' read -r REPO_TAG IMAGE_ID CREATED_AT; do
# 跳过悬空镜像(已在上一部分处理)
if [ "$REPO_TAG" = "<none>:<none>" ]; then
continue
fi
# 检查镜像是否被任何容器引用
IS_USED=false
for used_image in $USED_IMAGES; do
if [ "$used_image" = "$IMAGE_ID" ] || [ "$used_image" = "$REPO_TAG" ]; then
IS_USED=true
break
fi
done
if [ "$IS_USED" = false ]; then
# 计算镜像创建时间与当前时间的差值(秒)
CREATED_EPOCH=$(date -d "$CREATED_AT" +%s)
NOW_EPOCH=$(date +%s)
AGE_DAYS=$(( (NOW_EPOCH - CREATED_EPOCH) / 86400 ))
if [ $AGE_DAYS -gt $KEEP_DAYS ]; then
if [ "$DRY_RUN" = false ]; then
docker rmi $IMAGE_ID 2>&1 | tee -a $LOG_FILE
else
echo "模拟删除镜像:$REPO_TAG (ID: $IMAGE_ID, 已存在 $AGE_DAYS 天)" | tee -a $LOG_FILE
fi
fi
fi
done <<< "$TOTAL_IMAGES"
# ---------- 完成 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 清理完成。" | tee -a $LOG_FILE
使用说明:
- 赋予执行权限:
chmod +x docker-cleanup.sh - 安全测试:修改
DRY_RUN=true查看模拟结果。 - 正式运行:
./docker-cleanup.sh
自动化与定时任务集成
将脚本加入cron实现每周自动清理(例如每周日凌晨3点):
crontab -e # 添加以下行: 0 3 * * 0 /path/to/docker-cleanup.sh > /dev/null 2>&1
注意:生产环境建议先手动运行一次,确认无异常后再启用cron,日志文件/var/log/docker-cleanup.log会记录每一次操作。
常见问题解答(FAQ)
Q1:如何只清理未被任何容器使用的镜像?
A:可使用docker image prune -a,但这会清理所有未引用镜像(包括最近创建的),若需按时间过滤,采用上述脚本中的“按时间清理未引用镜像”部分,通过检查docker ps -a的镜像引用列表实现。
Q2:清理后镜像可以恢复吗?
A:除非镜像已推送到远程仓库(如Docker Hub或私有Registry),否则本地删除后无法恢复,建议在清理前执行docker save备份关键镜像。
Q3:大规模清理时如何防止脚本超时?
A:对于数千个镜像,可添加timeout命令限制单个docker rmi操作时间:
timeout 30 docker rmi $IMAGE_ID 2>&1 | tee -a $LOG_FILE
同时检查docker system df命令预估空间占用,避免同时删除过多资源导致I/O突增。
最佳实践与安全建议
- 始终先模拟运行:将
DRY_RUN=true设为默认,查看日志后再改为false。 - 使用白名单保护:如果需要保留特定镜像(如
nginx:latest),在脚本中添加排除逻辑:if [[ "$REPO_TAG" == "nginx"* ]]; then continue; fi
- 关联容器状态:清理镜像前,先清理已停止的容器(容器层数据可能包含仅被该容器引用的镜像层引用)。
- 监控磁盘使用:集成
df -h /var/lib/docker到日志中,跟踪清理效果。 - 避免使用
docker system prune -a --volumes:该命令会删除所有未使用的卷,可能导致数据丢失,专用脚本更安全。
通过以上Shell脚本,您可以精准控制Docker资源的清理范围,在释放磁盘空间的同时避免误删关键数据,根据实际环境调整KEEP_DAYS和DRY_RUN参数,即可满足开发、测试及生产环境的多样化需求。