Docker 多环境镜像管理:dev/staging/prod 策略、tag 规范与版本治理
系统讲解企业 Docker 镜像的多环境管理体系:dev/staging/prod 三环境镜像策略设计、语义版本 tag 规范(不同场景选择的 tag 类型)、Harbor 项目空间与清理策略(保留最近 N 个版本)、镜像版本晋升流程(同一个 SHA 在不同环境复用)、发布追踪(从 commit 到生产的完整链路),以及镜像管理的自动化脚本与审计实践。
镜像管理是企业容器化运维中最容易失控的环节:没有规范时,仓库里充斥着含义不明的 latest、test、new、final2 等 tag,出了问题完全无法追溯。本文建立一套系统的多环境镜像管理体系。
核心原则:镜像不可变
错误做法(不可变原则被违反):
开发 push → harbor/backend/api:latest
测试 pull → harbor/backend/api:latest
生产 pull → harbor/backend/api:latest
问题:latest 随时被覆盖,无法知道生产跑的是哪个版本
正确做法:
每次构建产生唯一 tag(commit SHA)
不同环境用不同的 tag 别名指向同一个镜像
生产只运行经过完整测试的镜像
镜像一旦构建,内容不变(immutable)
只有 tag 可以新增或移动(指向不同 SHA)
一、Tag 体系设计
四类 Tag 的职责
1. 构建 Tag(不可变,长期保留)
格式:{commit-sha}
示例:harbor.company.com/backend/api:a1b2c3d4
特点:
- 与 git commit 一一对应
- 永远不会被覆盖
- 生命周期跟随版本保留策略
2. 版本 Tag(不可变,发布时创建)
格式:v{major}.{minor}.{patch}
示例:harbor.company.com/backend/api:v1.2.3
特点:
- 与 git tag 对应
- 永远不会被覆盖
- 长期保留
3. 环境 Tag(可变,指向当前部署的版本)
格式:{env}
示例:staging、production
特点:
- 随每次部署更新
- 快速查看各环境当前版本
- 不用于部署决策(只用于观察)
4. 流动 Tag(可变,指向最新状态)
格式:latest、main、edge
示例:harbor.company.com/backend/api:latest
特点:
- 只用于开发环境 pull(方便)
- 生产禁止使用
实际 Tag 示例
# 一次完整发布后,Harbor 中的 tag 状态:
harbor.company.com/backend/api
a1b2c3d4 → Digest sha256:abc... ← commit SHA(不变)
v1.2.3 → Digest sha256:abc... ← 同一个镜像
v1.2 → Digest sha256:abc... ← 次版本别名
v1 → Digest sha256:abc... ← 主版本别名
production → Digest sha256:abc... ← 当前生产版本
staging → Digest sha256:bbb... ← 当前测试版本(不同版本)
latest → Digest sha256:bbb... ← main 分支最新
# 查看所有 tag 对应的 digest
docker buildx imagetools inspect harbor.company.com/backend/api \
--format '{{range .Manifest.Manifests}}{{.Digest}} {{.Platform.OS}}/{{.Platform.Architecture}}{{"\n"}}{{end}}'
二、Harbor 项目空间规划
Harbor 项目结构(按团队和用途分类):
harbor.company.com/
├── backend/ ← 后端服务镜像(生产就绪)
│ ├── api
│ ├── worker
│ └── scheduler
├── frontend/ ← 前端服务镜像
│ └── web
├── infra/ ← 基础设施镜像
│ ├── nginx
│ ├── mysql
│ └── redis
├── cache/ ← BuildKit 构建缓存(不是真实镜像)
│ ├── api:buildcache
│ └── worker:buildcache
└── dev/ ← 开发测试镜像(允许宽松,定期清理)
├── api
└── web
项目权限设置:
backend/ frontend/:开发者可推送,CI 机器人可推送/拉取
infra/:只有运维管理员可推送
cache/:CI 机器人读写
dev/:所有开发者读写
三、版本晋升流程
正确的版本晋升不是"每个环境分别构建",
而是"同一个镜像在不同环境晋升":
代码合并到 main
↓
CI 构建镜像(一次)
↓
镜像 push 到 Harbor(tag: abc1234)
↓
自动部署到 dev 环境
↓ 通过冒烟测试
staging 环境拉取相同 SHA 的镜像
↓ 通过集成测试
生产环境拉取相同 SHA 的镜像
关键点:三个环境跑的是完全相同的镜像层(相同 SHA)
只是部署的配置不同(环境变量/资源限制/副本数)
# 版本晋升脚本
#!/bin/bash
# promote.sh — 将镜像从 staging 晋升到 production
SERVICE=$1
VERSION=$2
REGISTRY=harbor.company.com
NAMESPACE=backend
STAGING_IMAGE="${REGISTRY}/${NAMESPACE}/${SERVICE}:${VERSION}"
PROD_IMAGE="${REGISTRY}/${NAMESPACE}/${SERVICE}:${VERSION}"
echo "晋升 ${SERVICE}:${VERSION} 到生产环境"
# 验证镜像存在
docker manifest inspect ${STAGING_IMAGE} > /dev/null 2>&1 || {
echo "❌ 镜像不存在:${STAGING_IMAGE}"
exit 1
}
# 更新 production tag(只是添加 tag,不重新构建)
docker buildx imagetools create \
--tag ${REGISTRY}/${NAMESPACE}/${SERVICE}:production \
${STAGING_IMAGE}
# 部署
docker service update \
--image ${PROD_IMAGE} \
--with-registry-auth \
${SERVICE//-/_}_api # 转换 service 名格式
# 记录晋升日志
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) promote ${SERVICE}:${VERSION} → production" \
>> /var/log/promotions.log
echo "✅ 晋升完成"
四、清理策略(避免仓库无限增长)
Harbor Retention Policy(保留策略)
Harbor Web UI 配置路径:
项目 → 策略 → 添加保留规则
推荐规则(backend 项目):
规则1:保留最近 20 个 tag(按推送时间)
作用于:除 v*/production/staging 之外的 tag
→ 保留最近 20 次 CI 构建的镜像
规则2:永久保留语义版本 tag
匹配 tag 格式:v**
→ v1.0.0, v1.1.0, v2.0.0 等永久保留
规则3:保留最近 10 个 staging tag
匹配 tag 格式:staging-*
→ 保留最近 10 次 staging 部署记录
# Harbor API 设置保留策略
curl -X POST https://harbor.company.com/api/v2.0/projects/backend/immutabletags \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"selector": {
"kind": "doublestar",
"decoration": "matches",
"pattern": "v**"
},
"tag_selectors": [{
"kind": "doublestar",
"decoration": "matches",
"pattern": "**"
}]
}'
# 创建标签保留规则(保留最近 20 个)
curl -X POST https://harbor.company.com/api/v2.0/projects/backend/retention \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"algorithm": "or",
"rules": [
{
"priority": 1,
"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": "**"}]}
},
{
"priority": 2,
"disabled": false,
"action": "retain",
"template": "always",
"params": {},
"tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "v**"}],
"scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
}
],
"trigger": {
"kind": "Schedule",
"settings": {"cron": "0 2 * * 0"}
}
}'
本地镜像清理
# 清理脚本(在 CI runner 和生产节点定期执行)
#!/bin/bash
# docker-cleanup.sh
echo "=== $(date) Docker 清理开始 ==="
# 清理停止的容器
STOPPED=$(docker ps -aq --filter status=exited | wc -l)
if [ "$STOPPED" -gt 0 ]; then
echo "清理 $STOPPED 个已停止的容器"
docker container prune -f
fi
# 清理悬空镜像(<none>:<none>)
DANGLING=$(docker images -q --filter dangling=true | wc -l)
if [ "$DANGLING" -gt 0 ]; then
echo "清理 $DANGLING 个悬空镜像"
docker image prune -f
fi
# 清理未使用的构建缓存(保留 10GB)
docker buildx prune -f --keep-storage=10gb
# 清理未使用的网络
docker network prune -f
# 查看清理效果
docker system df
echo "=== 清理完成 ==="
# 每周日凌晨 3 点执行
echo "0 3 * * 0 root /usr/local/bin/docker-cleanup.sh >> /var/log/docker-cleanup.log 2>&1" \
> /etc/cron.d/docker-cleanup
五、版本追踪与审计
# 查询:生产运行的是哪个 commit?
PROD_DIGEST=$(docker service inspect --format \
'{{.Spec.TaskTemplate.ContainerSpec.Image}}' backend_api \
| awk -F@ '{print $2}')
# 通过 Harbor API 查找对应的 commit
curl -s https://harbor.company.com/api/v2.0/projects/backend/repositories/api/artifacts \
-u admin:Harbor12345 \
| jq --arg digest "$PROD_DIGEST" \
'.[] | select(.digest == $digest) | {digest: .digest, tags: [.tags[].name], created: .push_time}'
# 输出:
# {
# "digest": "sha256:abc...",
# "tags": ["v1.2.3", "production", "a1b2c3d4"],
# "created": "2026-05-28T08:00:00.000Z"
# }
# → 从 tag a1b2c3d4 可以查到对应的 git commit
镜像 Label 追踪(构建时注入)
# Dockerfile 注入追踪信息
ARG GIT_COMMIT
ARG BUILD_DATE
ARG APP_VERSION
LABEL org.opencontainers.image.revision="${GIT_COMMIT}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.version="${APP_VERSION}" \
org.opencontainers.image.source="https://gitlab.company.com/backend/api" \
com.company.deployed-by="${GITLAB_USER_LOGIN:-ci}" \
com.company.pipeline-id="${CI_PIPELINE_ID:-local}"
# 运行时查看镜像元信息
docker inspect harbor.company.com/backend/api:v1.2.3 \
--format '{{json .Config.Labels}}' | jq .
# 输出:
# {
# "org.opencontainers.image.revision": "a1b2c3d4e5f6...",
# "org.opencontainers.image.created": "2026-05-28T08:00:00Z",
# "org.opencontainers.image.version": "v1.2.3",
# "com.company.pipeline-id": "12345"
# }
六、多环境 docker-compose 配置管理
# 项目结构
myapp/
├── compose.yml # 通用定义
├── compose.dev.yml # 开发环境:挂载代码,DEBUG 模式
├── compose.staging.yml # 测试环境:固定 SHA,真实数据库
├── compose.prod.yml # 生产环境:资源限制,Secrets
├── .env # 默认变量(开发)
├── .env.staging # 测试变量
└── .env.prod # 生产变量(不提交 git)
# 部署脚本(不同环境用不同配置)
#!/bin/bash
# deploy.sh <env> <version>
ENV=$1
VERSION=$2
case $ENV in
dev)
docker compose -f compose.yml -f compose.dev.yml up -d
;;
staging)
export APP_VERSION=$VERSION
docker compose \
-f compose.yml \
-f compose.staging.yml \
--env-file .env.staging \
up -d
;;
prod)
export APP_VERSION=$VERSION
docker compose \
-f compose.yml \
-f compose.prod.yml \
--env-file .env.prod \
up -d --no-deps api # 只更新 api,不动数据库
;;
*)
echo "用法: $0 <dev|staging|prod> <version>"
exit 1
;;
esac
echo "✅ ${ENV} 环境部署完成 (version: ${VERSION})"
小结
多环境镜像管理体系的四个核心:
Tag 不混用:commit SHA 用于追踪,语义版本用于发布,环境 tag(staging/production)用于观察,latest 只在开发用。
镜像不重建:同一个 commit 产生的镜像在所有环境用同一个 SHA,不同环境的差异只在配置(环境变量/资源/副本数),不在镜像。
清理自动化:Harbor 保留策略 + 本地 cron 清理,保留语义版本(v*)+ 最近 N 次构建,定期 GC 避免磁盘无限增长。
全链路可追溯:Dockerfile LABEL 注入 commit SHA + pipeline ID,任何时候都能从运行中的容器找到对应的 git commit 和 CI 流水线记录。
