根本原因是代理服务未正确处理 manifest 可变性与内容寻址冲突,导致 tag 覆盖时旧层与新 manifest 混合;harbor 需设 force_tag_pull: false 并清空旧缓存,nginx 需透传 accept/prefer/authorization 头,ci/cd 必须改用 digest 拉取。

为什么镜像代理缓存会返回错误版本?
根本原因不是缓存“坏了”,而是代理服务(如 Harbor、Nginx 反向代理、或自研 registry proxy)没有正确处理 manifest 的可变性与内容寻址冲突。Docker 镜像的 tag 是可变指针,而 layer blob 是不可变的 SHA256 值;当上游仓库(比如 Docker Hub 或 nvcr.io)用同一 tag 覆盖新镜像时,代理若未强制校验 manifest digest 或未刷新 tag→digest 映射,就会把旧层和新 manifest 拼在一起,导致拉下来的镜像是“混合体”——容器能启动,但行为异常,且 docker inspect 查到的 RepoDigests 和实际内容不匹配。
Harbor Proxy Cache 必须关闭 tag 覆盖并启用 digest 校验
Harbor 默认的 proxy cache 在同步时仍信任上游 tag 的“权威性”,不校验 manifest 是否变更。必须手动干预:
- 在
harbor.yml中显式禁用 tag 覆盖:proxy: cached_registries: - name: docker-io endpoint: https://registry-1.docker.io verify_remote_cert: true # 关键:禁止用本地缓存覆盖上游 tag 指向 force_tag_pull: false - 重启 Harbor 后,所有拉取请求将携带
Accept: application/vnd.docker.distribution.manifest.v2+json并附带Prefer: safe头,触发 Harbor 对 manifest digest 的逐层比对 - 验证是否生效:拉取一个已知被覆盖过的 tag(如
python:3.9-slim),执行docker pull your-harbor.com/library/python:3.9-slim,再运行docker inspect python:3.9-slim | grep RepoDigests—— 输出应与直接从 Docker Hub 拉取的RepoDigests完全一致
自建 Nginx/registry 反向代理必须透传 Authorization 和 Accept 头
很多团队用 Nginx 做轻量代理,但默认配置会丢弃关键请求头,导致 upstream 无法识别 client 的 digest 请求意图,降级为 tag-only 拉取:
- 确保 Nginx 配置中包含:
proxy_pass_request_headers on; proxy_set_header Authorization $http_authorization; proxy_set_header Accept $http_accept; proxy_set_header Prefer $http_prefer;
- 禁用 Nginx 缓存
/v2/*/manifests/*路径:该路径返回的是 JSON manifest,大小固定但语义敏感,缓存它等于固化错误映射 - 检查
curl -I -H "Accept: application/vnd.docker.distribution.manifest.v2+json" https://your-proxy/v2/library/nginx/manifests/latest响应头是否含Docker-Content-Digest—— 若无,说明代理未透传或上游拒绝了 digest 请求
CI/CD 流水线里必须用 digest 而非 tag 拉取镜像
即使代理层修复了,如果构建脚本仍写 FROM nginx:latest 或 docker pull redis:7.2,就等于主动绕过所有一致性保障。生产环境唯一可信的拉取方式是基于 digest:
- 在 CI 中先查 manifest digest:
curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ -H "Authorization: Bearer $(get_token)" \ https://registry-1.docker.io/v2/library/nginx/manifests/1.25.4 | jq -r '.signatures[0].header.digest'
- 用 digest 构建:
FROM nginx@sha256:abc123...或docker pull nginx@sha256:abc123... - Kubernetes Deployment 中也替换 image 字段:
image: nginx@sha256:abc123...—— 这样即使 Harbor 缓存失效、甚至代理宕机,只要 registry-1.docker.io 可达,拉取结果仍确定
最易被忽略的一点:Harbor 的 force_tag_pull: false 不会自动清理已有缓存,旧 tag 映射仍存在。上线前必须手动清空对应 project 的 proxy cache 数据目录,或调用 Harbor API 删除缓存条目,否则“修复”只是给新拉取加保险,旧错仍潜伏。











