You’ve staged 40 container images for a dark site. The VKS bundle loads
into Harbor first time with imgpkg copy --tar. The GPU Operator images,
same tool and same command, fail. Both are tarballs. Both contain the
images. What’s different?
$ tar -tf images/nvidia_gpu-operator__v26.3.3.tar | grep -v ^blobs/
oci-layout
index.json
$ tar -tf supervisor/vks-3.7.1.tar | head -2
manifest.json
sha256-05e0b744....tar.gz
That’s the whole answer. It took me an afternoon to find, which is a generous amount of afternoon for a couple of filenames.
Two layouts that look the same from the outside
| Archive | Layout | imgpkg copy --tar? |
|---|---|---|
Written by a registry-API puller / crane pull --format=oci / skopeo | OCI image layout: oci-layout, index.json, blobs/sha256/* | No |
Written by imgpkg copy --to-tar | imgpkg tar: manifest.json, sha256-*.tar.gz | Yes |
imgpkg reads only the tar format imgpkg itself writes. There’s no
converter: imgpkg push -f <dir> would wrap the OCI directory as a new
image layer, not replicate the original image. If your staging pipeline
pulled with anything other than imgpkg, imgpkg won’t load it. Full stop.
Same tool, same command, different archive.
tar -tf tells you which one you’ve got.
Three ways out
1. Re-pull with imgpkg (chosen, when you still have internet somewhere):
imgpkg copy -i nvcr.io/nvidia/gpu-operator:v26.3.3 --to-tar gpu-operator.tar
# at the dark site:
imgpkg copy --tar gpu-operator.tar --to-repo harbor02/nvidia/gpu-operator
The cost: a second download, and it’s bigger. imgpkg copy has no
platform filter, so you get every architecture in the manifest list
(amd64 and arm64, sometimes s390x and ppc64le). For the GPU Operator set,
1.1 GiB became 2.3 GiB. The upside: your arm64 node pool, if you ever have
one, won’t send you back to the internet.
2. crane, no re-pull. crane is a static Go binary (no install, no
root), and it reads OCI layouts natively:
crane push images/nvidia_gpu-operator__v26.3.3.tar harbor02/nvidia/gpu-operator:v26.3.3
It’s the cheapest in bytes. It does mean getting one more binary through the airlock.
3. skopeo, if it happens to be there. skopeo copy oci-archive:... docker://....
Usually it isn’t.
(There’s a fourth: stand up a local registry:2, crane-push into it, then
imgpkg copy -i localhost:5000/... --to-repo harbor02/.... It’s strictly
more work than 1 or 2. Don’t.)
What survives the copy (verified, not assumed)
I tested this against a throwaway crane registry serve with a real
multi-arch image, because “the digest should be preserved” is the kind of
sentence that precedes a bad week.
imgpkg copy -i nvcr.io/nvidia/cloud-native/k8s-mig-manager:v0.13.1 --to-tar probe.tar
imgpkg copy --tar probe.tar --to-repo 127.0.0.1:5099/real/k8s-mig-manager
imgpkg tag list -i 127.0.0.1:5099/real/k8s-mig-manager --digests
v0.13.1 sha256:8e0803d2... # manifest-list digest
crane digest nvcr.io/nvidia/cloud-native/k8s-mig-manager:v0.13.1
sha256:8e0803d2... # identical
crane digest --platform linux/amd64 127.0.0.1:5099/real/k8s-mig-manager:v0.13.1
sha256:69250353... # identical to the OCI-archive .digest sidecar
That settles three things:
- The human tag survives. The destination gets
:v0.13.1and imgpkg’s:sha256-<hex>.imgpkg. This matters, because Helm values reference images by tag. (Only--repo-based-tagschanges the tag shape; don’t pass it.) - Digests survive at both the manifest-list and per-platform level.
- The amd64 bits are identical to the OCI archive. A re-pull is a re-packaging, not different content.
One trap: pinning by digest (imgpkg copy -i repo@sha256:...) would
keep it single-arch and small. But then there’s no tag to carry across.
The destination only gets :sha256-<hex>.imgpkg, and your Helm values
break. Don’t do that either.
The one that bites later: signatures
imgpkg drops cosign signatures by default at every copy. That matters when the thing you’re loading is a platform bundle whose consumer checks signatures. VKS supervisor services on VCF 9.0.1+ do, for versions above 3.4.0 from a private registry.
Every content check passes and the activation proceeds, so for a while everyone is happy. Then every new control-plane pod is denied by a webhook, for running “in an untrusted namespace”.
Pull and push both need --cosign-signatures. The .sig artifacts are
separate tags (sha256-<digest>.sig), about 100 KB for a whole bundle. So
if the content is already in the registry, you can ship just the
signatures.
That one gets its own post.
Why this matters outside the lab
Air-gapped customers pay for every failed transfer twice: once in the change window that slipped, and once in the import/export cycle to try again.
Staging discipline is the whole game. Know which tool wrote each archive, and what survives a copy. Prove the load against a throwaway registry before anything crosses the airlock. That rehearsal is a fixed part of how we prepare dark-site deliveries, and it’s why the loading step is the boring part on the day. Boring is underrated at a dark site.
Rules learned
- Run
tar -tf x.tar | headbefore anything else.manifest.jsonmeans imgpkg;oci-layoutmeans an OCI archive. They are not interchangeable. - imgpkg loads only imgpkg tars. No converter exists.
- OCI archives load with
crane push(static binary) or skopeo. imgpkg copycopies all platforms (bigger) and preserves tags and digests (verified). Pin by tag, never by digest, when Helm values reference tags.- Add
--cosign-signatureson both ends when the consumer verifies bundles. - Stage
imgpkgandcranebinaries with the tarballs. One of them will be the wrong tool for something.
Broadcom documentation
- Push Standard Packages to a Private Harbor Registry:
imgpkg copy --to-taron the connected side,--tar ... --to-repointo Harbor - Supporting Private Registries: relocating Carvel bundles, and why omitting
--cosign-signaturesleaves a bundle untrusted - Upgrade VKS from a Private Registry: the VKS bundle copied with
--cosign-signatureson both copies - vSphere kubernetes service upgrade failed with “kapp: error waiting on reconcile packageinstall”: the untrusted-namespace denial when the signatures were dropped
- Script to mirror resources for VPAIF-N AI workloads in air-gapped environment: Broadcom’s Docker-based script for mirroring Private AI container images, Helm charts and models into Harbor
Lab environment; opinions my own. Verified against a local registry with real multi-arch images; digests shown are truncated.