Docker 镜像生命周期管理:自动清理策略、Harbor GC 与镜像版本治理
系统讲解 Docker 镜像生命周期的全链路管理:本地镜像的自动清理(定时 cron + docker system prune)、Harbor 私有仓库的 GC 原理与配置(标记删除 vs 实际回收两阶段)、镜像保留策略的设计(语义版本永久保留 + 构建版本保留最近 N 个)、BuildKit 构建缓存的清理与保留、镜像晋升与废弃的治理流程,以及各环境镜像的生命周期差异(dev/staging/prod 不同保留策略)。
没有清理策略的镜像仓库是“垃圾场”:Harbor 磁盘爆满、CI runner 磁盘空间耗尽、工程师不知道哪个 tag 在生产运行——这些问题都源于缺乏系统性的镜像生命周期管理。本文建立一套从本地到仓库的完整清理与治理方案。
镜像生命周期全貌
构建 存储 部署 清理
────────────────────────────────────────────────────────────────────────
CI 构建 Harbor 仓库 生产运行中 过期清理
↓ push ├── v1.0.0 (永久) ↓ GC 回收
本地构建缓存 ├── v1.1.0 (永久) 环境 tag 标记 BuildKit prune
↓ ├── abc1234 (30天) production/staging 本地 prune
CI runner 缓存 ├── def5678 (30天)
└── latest (7天)
一、本地镜像清理
1.1 手动清理命令
# 查看当前磁盘占用(操作前必看)
docker system df
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 67 8 23.4GB 18.2GB (78%)
# Containers 12 4 1.2GB 0.8GB (67%)
# Local Volumes 8 3 45GB 20GB (44%)
# Build Cache - - 4.3GB 4.3GB
# 分步清理(生产操作,不要一把 prune -a)
# 步骤1:清理停止的容器(安全,无数据丢失风险)
docker container prune -f
# 步骤2:清理悬空镜像(<none>:<none>,安全)
docker image prune -f
# 步骤3:清理未使用的 volume(危险!会删数据,先确认)
docker volume ls --filter dangling=true # 先列出
docker volume prune -f # 再清理
# 步骤4:清理 BuildKit 缓存(控制保留量)
docker buildx prune --keep-storage=10gb -f
# 步骤5:清理所有未使用的镜像(包括有 tag 但没有容器引用的)
# 危险!会删除所有未运行容器引用的镜像,包括预拉取的基础镜像
docker image prune -a -f
# 全量清理(危险!谨慎执行)
docker system prune -a --volumes -f
1.2 自动清理脚本
#!/bin/bash
# /usr/local/bin/docker-cleanup.sh
# 定时执行,安全地清理 Docker 资源
set -e
LOG_FILE=/var/log/docker-cleanup.log
exec >> "$LOG_FILE" 2>&1
echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) 开始清理 ==="
# 清理前磁盘使用
BEFORE=$(docker system df --format '{{.Size}}' | paste -sd+ | bc 2>/dev/null || echo "N/A")
echo "清理前总占用:$(docker system df | grep -E 'Images|Containers|Volumes|Build Cache' | awk '{sum+=$4} END{print sum/1024/1024 "GB"}')"
# 清理停止的容器(超过 24 小时的)
echo "[容器] 清理已停止 24 小时以上的容器..."
docker container prune -f --filter "until=24h"
# 清理悬空镜像
echo "[镜像] 清理悬空镜像..."
DANGLING=$(docker images -q --filter dangling=true | wc -l)
if [ "$DANGLING" -gt 0 ]; then
docker image prune -f
echo " 已清理 $DANGLING 个悬空镜像"
fi
# 清理超过 7 天的未使用镜像(dev 环境可以更激进)
# 生产机器不执行这步,避免误删预热镜像
if [ "${ENVIRONMENT}" = "ci" ] || [ "${ENVIRONMENT}" = "dev" ]; then
echo "[镜像] CI/Dev 环境:清理 7 天以上未使用的镜像..."
docker image prune -a -f --filter "until=168h" # 168h = 7天
fi
# 清理 BuildKit 缓存(保留最近 10GB)
echo "[构建缓存] 清理 BuildKit 缓存(保留 10GB)..."
docker buildx prune -f --keep-storage=10gb 2>/dev/null || true
# 清理未使用的网络
echo "[网络] 清理未使用的 Docker 网络..."
docker network prune -f
# 查看清理后效果
echo "清理后磁盘使用:"
docker system df
echo "=== 清理完成 ==="
# 设置 cron(每天凌晨 3 点执行)
cat > /etc/cron.d/docker-cleanup << 'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
ENVIRONMENT=production
0 3 * * * root /usr/local/bin/docker-cleanup.sh
EOF
chmod 644 /etc/cron.d/docker-cleanup
chmod +x /usr/local/bin/docker-cleanup.sh
二、Harbor GC(垃圾回收)
2.1 Harbor 删除的两阶段
Harbor 的镜像删除是两阶段操作:
阶段1:标记删除(立即生效)
删除 tag → Harbor 数据库标记该 manifest 为待删除
Harbor UI 中不再显示该 tag
但底层 blob(实际镜像层数据)仍在磁盘上!
为什么不立即删除 blob?
→ 多个镜像可能共享同一个 layer(同一个 blob)
→ 必须确认没有任何引用后才能安全删除
阶段2:GC(实际磁盘回收)
Harbor 扫描所有 manifest → 找出没有任何引用的 blob
删除孤立 blob → 磁盘空间真正被释放
GC 期间 Harbor 进入只读模式(不能 push)
→ 建议在业务低峰期(如凌晨)执行
2.2 GC 配置(Harbor UI)
管理入口:
Harbor Web UI → 系统管理 → 垃圾清理
配置项:
┌─────────────────────────────────────────────────┐
│ 调度(Schedule) │
│ ○ 无调度(手动触发) │
│ ● 每天(Daily) 时间: 02:00 │
│ ○ 每周(Weekly) │
│ ○ 自定义 Cron │
│ │
│ 删除未打 tag 的 manifest: ✓ │
│ 并发数: 2 │
└─────────────────────────────────────────────────┘
2.3 Harbor API 触发 GC
# 手动触发 GC(维护窗口时执行)
HARBOR_URL=https://harbor.company.com
HARBOR_USER=admin
HARBOR_PASS=Harbor12345
# 触发 GC
curl -X POST \
-u "${HARBOR_USER}:${HARBOR_PASS}" \
-H "Content-Type: application/json" \
"${HARBOR_URL}/api/v2.0/system/gc/schedule" \
-d '{
"schedule": {
"type": "Manual"
},
"delete_untagged": true
}'
# 查看 GC 状态
curl -s \
-u "${HARBOR_USER}:${HARBOR_PASS}" \
"${HARBOR_URL}/api/v2.0/system/gc" \
| jq '.[] | {id: .id, status: .job_status, freed: .job_parameters}'
# 查看 GC 日志
curl -s \
-u "${HARBOR_USER}:${HARBOR_PASS}" \
"${HARBOR_URL}/api/v2.0/system/gc/1/log" | head -50
三、镜像保留策略设计
3.1 Harbor 保留策略(Retention Policy)
# 通过 Harbor API 创建保留策略
# 推荐策略组合(backend 项目):
# 规则1:永久保留语义版本 tag(v*)
# 规则2:保留最近 20 个构建 tag(非 v* 前缀)
# 规则3:dev 项目:保留最近 10 个(快速过期)
HARBOR_URL=https://harbor.company.com
PROJECT_ID=2 # backend 项目的 ID(从 Harbor API 获取)
curl -X POST \
-u admin:Harbor12345 \
-H "Content-Type: application/json" \
"${HARBOR_URL}/api/v2.0/retentions" \
-d '{
"algorithm": "or",
"rules": [
{
"priority": 1,
"disabled": false,
"action": "retain",
"template": "always",
"tag_selectors": [{
"kind": "doublestar",
"decoration": "matches",
"pattern": "v**"
}],
"scope_selectors": {
"repository": [{
"kind": "doublestar",
"decoration": "repoMatches",
"pattern": "**"
}]
}
},
{
"priority": 2,
"disabled": false,
"action": "retain",
"template": "latestActiveK",
"params": {"latestN": 20},
"tag_selectors": [{
"kind": "doublestar",
"decoration": "excludes",
"pattern": "v**"
}],
"scope_selectors": {
"repository": [{
"kind": "doublestar",
"decoration": "repoMatches",
"pattern": "**"
}]
}
}
],
"trigger": {
"kind": "Schedule",
"settings": {"cron": "0 1 * * 0"}
},
"scope": {
"level": "project",
"ref": 2
}
}'
3.2 各环境镜像保留策略对比
环境 保留周期 策略
──────────────────────────────────────────────────────
生产(prod)
语义版本 永久 v* 匹配,永不删除
构建 tag 60 天 commit SHA,部署后 60 天
environment 保留最近 10 production/staging tag
测试(staging)
构建 tag 30 天 commit SHA,30 天
latest 永远 1 个 每次覆盖
开发(dev)
构建 tag 7 天 快速过期
feature/ 3 天 特性分支镜像
latest 永远 1 个
构建缓存
buildcache 不限 永远保留(加速构建)
(只有 blob 有 GC)
3.3 不可变 tag(防止覆盖)
# 为语义版本 tag 设置不可变规则(Immutable Tag)
# Harbor UI:项目 → 配置 → 不可变标签
# API 方式
curl -X POST \
-u admin:Harbor12345 \
-H "Content-Type: application/json" \
"${HARBOR_URL}/api/v2.0/projects/backend/immutabletags" \
-d '{
"selector": {
"kind": "doublestar",
"decoration": "repoMatches",
"pattern": "**"
},
"tag_selectors": [{
"kind": "doublestar",
"decoration": "matches",
"pattern": "v**"
}]
}'
# 验证:尝试推送同名 tag 会失败
docker tag myapp:v1.0.0-new harbor.company.com/backend/api:v1.0.0
docker push harbor.company.com/backend/api:v1.0.0
# Error response from daemon: tag v1.0.0 is immutable
四、BuildKit 构建缓存管理
# 查看 BuildKit 缓存使用
docker buildx du
# ID RECLAIMABLE SIZE LAST ACCESSED
# md5xxxxxx true 1.2GB 2 hours ago
# sha256:yyy... true 850MB 1 day ago
# Total: 15.3GB
# 保留最近 N GB,清理其余
docker buildx prune --keep-storage=10gb -f
# 只清理超过 X 天的缓存
docker buildx prune --filter "until=168h" -f # 7 天
# 清理所有缓存(重置,构建速度暂时下降)
docker buildx prune -a -f
# CI 中的缓存策略(GitLab CI 示例)
# .gitlab-ci.yml
build:
script:
- |
docker buildx build \
--cache-from type=registry,ref=harbor.company.com/cache/api:buildcache \
--cache-to type=registry,ref=harbor.company.com/cache/api:buildcache,mode=max \
-t harbor.company.com/backend/api:${CI_COMMIT_SHORT_SHA} \
--push .
# CI runner 本地缓存(runner 磁盘不超 20GB)
- docker buildx prune --keep-storage=20gb -f
五、镜像废弃与下架流程
#!/bin/bash
# deprecate-image.sh — 将镜像标记为废弃并通知团队
# 用于废弃旧版本,但不立即删除(给依赖方时间迁移)
IMAGE=$1 # harbor.company.com/backend/api
VERSION=$2 # v1.0.0
REASON=$3 # "已被 v1.1.0 替代"
HARBOR_URL=https://harbor.company.com
PROJECT=backend
REPO=api
echo "废弃镜像:${IMAGE}:${VERSION}"
echo "原因:${REASON}"
# 1. 获取镜像 digest
DIGEST=$(docker manifest inspect ${IMAGE}:${VERSION} \
--verbose 2>/dev/null | jq -r '.Descriptor.digest')
echo "对应 Digest:${DIGEST}"
# 2. 在 Harbor 中添加废弃标签(仅供参考,不阻断拉取)
# 使用自定义 label 标注废弃状态
docker buildx imagetools create \
--tag ${IMAGE}:${VERSION}-deprecated \
${IMAGE}:${VERSION}
# 3. 记录废弃日志
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) DEPRECATED ${IMAGE}:${VERSION} → ${REASON}" \
>> /var/log/image-lifecycle.log
# 4. 通知(发钉钉 webhook)
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=xxx"
curl -s -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d "{
\"msgtype\": \"text\",
\"text\": {
\"content\": \"[镜像废弃通知] ${IMAGE}:${VERSION} 已废弃。原因:${REASON}。请尽快迁移到新版本。\"
}
}"
echo "✅ 废弃流程完成"
echo " 30天后执行实际删除:harbor-delete-image.sh ${IMAGE} ${VERSION}"
六、磁盘使用监控
# Prometheus 监控 Harbor 磁盘使用率
# Harbor 暴露 /metrics 端点(需要在管理界面开启)
# Prometheus scrape 配置
# scrape_configs:
# - job_name: harbor
# static_configs:
# - targets: ['harbor.company.com:80']
# metrics_path: /metrics
# basic_auth:
# username: admin
# password: Harbor12345
# 关键指标
# harbor_storage_used_bytes → Harbor 已用存储
# harbor_db_latency_ms → 数据库延迟
# 告警规则
# - alert: HarborDiskUsageHigh
# expr: harbor_storage_used_bytes / harbor_storage_total_bytes > 0.80
# annotations:
# summary: "Harbor 磁盘使用率超过 80%,请执行 GC 或扩容"
# 也可以用脚本简单监控
check_harbor_disk() {
USAGE=$(df -h /data/harbor | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt 80 ]; then
echo "警告:Harbor 磁盘使用率 ${USAGE}%,请立即清理"
# 触发 GC
curl -X POST -u admin:Harbor12345 \
https://harbor.company.com/api/v2.0/system/gc/schedule \
-H "Content-Type: application/json" \
-d '{"schedule":{"type":"Manual"},"delete_untagged":true}'
fi
}
小结
镜像生命周期管理的核心是:让该保留的永远保留,让该清理的自动清理。
实际操作中,三类镜像处理方式截然不同:语义版本镜像(v1.2.3)永久保留,这是你能回溯历史版本和紧急回滚的保障;CI 构建镜像(commit SHA)保留最近 N 个即可;开发分支镜像 7 天自动过期。
Harbor GC 是“真正释放空间”的关键步骤——仅仅删除 tag 不会释放磁盘,必须运行 GC 才能回收 blob 层占用的空间。建议每周自动执行一次,在业务低峰时段运行(GC 期间 Harbor 只读)。
