结论先行
- Docker/OCI 镜像本质上是一组 layer(每层都是 tar 文件)+ manifest;
把它们再一起打包成一个单独的 tar 文件只是为了“离线传输”。 - 镜像仓库(Docker Hub、Harbor、ECR…)只接受符合 OCI Distribution API 的分层上传;它不会识别你手里的整个 tar 包。
因此:
• 常规推送必须用 docker push / nerdctl push / skopeo copy……
• 想用 tar 包就先 docker load 回本地,再 docker push。
────────────────────────────
一、镜像内部到底长啥样
────────────────────────────
• layerA.tar
• layerB.tar
• config.json
• manifest.json / index.json
这些文件再被 docker save 打成一个归档,如 myimage.tar。
本地 docker load 时只是把 tar 解开并写进 /var/lib/docker/overlay2/…;
push 到仓库时 Docker 会按 API 把每个 layer blob 单独 PUT。
────────────────────────────
二、为什么“直接推 tar”行不通
────────────────────────────
仓库端接口示例:
PUT /v2//blobs/uploads/ # 上传单层 layer
PUT /v2//manifests/ # 上传 manifest
整个流程需要多次 HTTP 请求和校验 digest。
把 myimage.tar 拿去 POST 一下,仓库完全不认识。
────────────────────────────
三、离线场景的正确姿势
────────────────────────────
-
机器 A(有网)
docker pull alpine:3.20 docker save alpine:3.20 -o alpine_3.20.tar scp alpine_3.20.tar user@B:/tmp
-
机器 B(无外网但能访问内网 Harbor)
docker load -i /tmp/alpine_3.20.tar # 重新导入为本地镜像 docker tag alpine:3.20 harbor.local/dev/alpine:3.20 docker push harbor.local/dev/alpine:3.20 # 正常推送
不想落地镜像引擎?可用 skopeo / crane:
# 直接把 tar 里的镜像推到仓库(skopeo 会自动拆开 layer)
skopeo copy oci-archive:./alpine_3.20.tar docker://harbor.local/dev/alpine:3.20
────────────────────────────
四、特殊情况
────────────────────────────
• Harbor 2.5+ 支持 Web UI 手动上传 OCI artifact,实质上它后台仍是把 tar 拆层,再写入 registry。
• 旧版本私服(如某些私有 Nexus)可能提供“上传 blob.tar.gz”接口,但同样会在服务端解包,不是真正意义的“一口气推 tar”。
────────────────────────────
总结
────────────────────────────
镜像“可被导出成 tar”,但仓库“只吃分层”。
要么先 load 再 push,要么用工具把 tar 里的层解析后按 OCI 协议上传;
直接把 *.tar POST 到 registry 是行不通的。