k8wiz
All graphs

Kubernetes graph

Inside an OCI image: tag, index, manifests, config and layers

How a registry stores a multi-platform container image, with its attestations and a signature, and how containerd walks from a tag to a running container's root filesystem.

A tag like ghcr.io/acme/shop:1.4.2 points at a tree of JSON documents and tarballs, each stored under the SHA-256 digest of its bytes. The index lists one manifest per platform. Each manifest lists a config and the layers, in the order they stack.

Signatures and SBOMs sit next to the image, not inside it, because adding anything to the image would change its digest. They point back at it with a subject field, and the registry's referrers API finds them. The article [OCI images explained](/articles/oci-images-explained-manifests-layers-media-types-and-artifacts) covers every object here in more detail.

Interactive graph

Click a resource to see what it does, or follow the setup step by step.

You write itComes with an add-onMade by a toolCreated by a controllerreferencestrafficcreates

How it works

All 13 resources and what they do

Pod shop-7d9f8c6b5-x2k4pv1
Asks for ghcr.io/acme/shop:1.4.2. Pinning @sha256:… instead would skip the tag lookup and make every node run exactly the same bytes.
Container runtime containerd
Pulls for the kubelet. It sends an Accept header listing every manifest format it understands, picks the manifest for the node's platform, downloads only the blobs its content store lacks, and checks each one against its digest and size.
Tag shop:1.4.2
A name that points at a digest, here the index's. Tags can be moved to another digest at any time, which is why 1.4.2 today may not be 1.4.2 tomorrow.
Image index sha256:5e1a…
Media type application/vnd.oci.image.index.v1+json. Lists one manifest per platform, plus attestation manifests that docker buildx adds. Pin this digest to keep the image multi-platform.
Image manifest linux/amd64 sha256:7d1c…
Media type application/vnd.oci.image.manifest.v1+json. One image for one platform: a config descriptor and three layer descriptors, bottom layer first.
Image manifest linux/arm64 sha256:e83a…
The arm64 build, with its own config and layers. An amd64 node never downloads any of it. A node whose platform isn't in the index fails with no match for platform in manifest.
Attestation manifest unknown/unknown sha256:0c4d…
Added by docker buildx for the amd64 image. Its layers have the media type application/vnd.in-toto+json and hold SLSA provenance and, if you asked for one, an SPDX SBOM. The unknown/unknown platform keeps runtimes from trying to run it.
Image config sha256:b5d2…
Media type application/vnd.oci.image.config.v1+json. What the runtime needs besides files: user, environment, entrypoint and working directory, plus rootfs.diff_ids, the digests of the uncompressed layers.
Layer #1 base OS, 29 MB
A gzipped tar of the Debian base filesystem. Every image built on the same base shares this blob in the registry and this snapshot on the node, so it's usually already there.
Layer #2 packages, 8 MB
Only the files that apt-get install added or changed. A file deleted here would show up as a whiteout entry named .wh.<name>, and the bytes in layer 1 would still be shipped.
Layer #3 app binary, 14 MB
The COPY of the application binary. It changes on every release, so it's last: rebuilding reuses layers 1 and 2, and a node that ran the previous version downloads only this one.
Signature cosign bundle
A separate manifest created by cosign sign. Its subject points at the index digest, so the registry lists it under GET /v2/acme/shop/referrers/sha256:5e1a…. Admission policies such as Kyverno's verifyImages look it up before a Pod may start.
Snapshots overlayfs root filesystem
containerd unpacks each layer into a snapshot on top of the one below, keyed by chain ID, and mounts them as overlay lower directories. The container gets an empty writable layer on top, which disappears with the container.

Common mistakes

  • exec format error at startup almost always means the image has no manifest for the node's platform, or one built for the wrong one. crane manifest ghcr.io/acme/shop:1.4.2 shows which platforms the index lists.
  • Pinning a single platform's manifest digest in a cluster with mixed architectures makes the Pods fail on the other nodes. Pin the index digest.
  • Deleting a file in a later Dockerfile step doesn't shrink the image. The layer that added it still ships, and the later layer adds a whiteout. Remove temporary files in the same RUN that created them.
  • A signature made for one digest doesn't cover another. Re-tagging and pushing rebuilt bytes needs a new signature, and so does a manifest that a registry or a copy tool rewrote.
  • With imagePullPolicy: IfNotPresent and a moving tag, nodes that pulled 1.4.2 earlier keep the old bytes. Use digests, or immutable tags enforced by the registry.

The manifests you write

Everything above that is marked "You write it", in one file. The rest comes from add-ons and controllers.

apiVersion: v1
kind: Pod
metadata:
  name: shop
  namespace: shop
spec:
  containers:
    - name: shop
      image: ghcr.io/acme/shop:1.4.2
      imagePullPolicy: IfNotPresent

Keep going

More graphs