skip to content

When a container image supports several CPU architectures, what does its tag actually point at, and how does that change which sha256 digest you should record when pinning?

level: seniorimportance: nice to knowfreq 30%

answer

  1. tag → index → per-platform manifest → layers
  2. index digest = multi-arch, still immutable
  3. platform digest hard-codes one CPU arch
  4. buildx imagetools inspect / crane digest, not docker inspect
  5. --load can't hold multi-platform; --push instead

basics

~20 s

The tag points at an image index (manifest list) — a small document listing one manifest digest per platform. Pin the index digest: it stays immutable while still resolving per architecture. A per-platform manifest digest also works but hard-codes one CPU architecture.

solid answer

~50 s

For a multi-platform image the tag resolves to an **OCI image index** (Docker calls it a manifest list). The index is a JSON document with an entry per platform — `linux/amd64`, `linux/arm64`, and so on — each entry holding the digest of that platform's image manifest. The client reads the index, picks the entry matching its own platform, then pulls that manifest and its layers. So there are two digests you could copy. The **index digest** is what the tag names; pinning it keeps the image immutable *and* multi-arch. A **per-platform manifest digest** is also a valid, pullable reference, but it names exactly one architecture — pin that and your arm64 builders either fail or silently produce amd64 images. The trap is tooling: `docker inspect .RepoDigests` on an amd64 host commonly yields the platform-specific digest. Use `docker buildx imagetools inspect <ref>` or `crane digest <ref>`, which report the index digest, and verify with `crane manifest` that what you pinned has a `manifests` array.

code

bash · 9 lines
bash
docker buildx imagetools inspect node:20-alpine
# Name:      docker.io/library/node:20-alpine
# MediaType: application/vnd.oci.image.index.v1+json
# Digest:    sha256:<INDEX DIGEST — pin this>
# Manifests:
#   Platform: linux/amd64   Digest: sha256:<amd64 manifest>
#   Platform: linux/arm64   Digest: sha256:<arm64 manifest>

crane manifest node:20-alpine | jq '.manifests[] | {platform, digest}'

go deeper

for a junior

Know that one tag can serve several CPU architectures and that Docker picks the right one at pull time.

for a middle

Describe the index/manifest-list structure and that the index digest is the one a tag names.

for a senior

Explain the pinning trap, how to read digests from the registry rather than the local store, and how indexes are assembled or re-tagged with buildx imagetools.

for a principal

Reason about the fleet consequences: signature and policy binding level, deployed-vs-running digest reconciliation, and whether to standardise on index-level pins across all repositories.

## The three-level name graph For a single-platform image: tag → manifest → (config blob + layer blobs). For a multi-platform image an extra level appears: tag → **index** → per-platform **manifest** → (config + layers). The index — `application/vnd.oci.image.index.v1+json`, or the older Docker `manifest.list.v2+json` — is a tiny document. Each element carries a `digest`, a `mediaType`, a `size`, and a `platform` object with `architecture`, `os`, and optionally `variant` (for example `arm64/v8`) and `os.version` (used by Windows images, where the host kernel version must match). It contains no layers of its own. When a client pulls `myapp:1.4`, it fetches the index, matches its own platform against the entries, and then fetches only that entry's manifest and layers. This is why `docker pull nginx` works identically on an Apple Silicon laptop and an x86 server: one name, platform-appropriate content. ## Which digest to pin Both digests are immutable content addresses; the difference is *what they address*. - **Index digest** — addresses the whole set. `myapp@sha256:<index>` still resolves per platform at pull time. This is almost always what you want in a `FROM` line, a deployment manifest, or a promotion command. - **Manifest digest** — addresses one platform's image. Useful deliberately: a build that must run on amd64 for reproducibility reasons, or a debug pull of a specific variant with `--platform`. The operational failure looks like this: an engineer on an amd64 machine runs `docker pull node:20-alpine` then `docker inspect --format '{{index .RepoDigests 0}}'`, pastes the result into the Dockerfile, and everything is fine until an arm64 CI runner or an Apple Silicon developer hits `no matching manifest for linux/arm64/v8 in the manifest list entries` — or worse, until a builder with emulation quietly produces an amd64 image that then runs under QEMU in production at a fraction of native speed. The safe habit: never harvest digests from a pulled local image. Query the registry. ## Inspecting properly `docker buildx imagetools inspect node:20-alpine` prints the index digest, the media type, and the platform table — without pulling layers, and independent of your host architecture. `crane digest node:20-alpine` prints the index digest; `crane manifest node:20-alpine | jq` shows the raw document, where the presence of a `manifests` array tells you it is an index rather than a single-platform manifest. `docker buildx imagetools inspect --raw` gives the same bytes that were hashed, which is how you can convince yourself the digest really is the hash of that document. ## Building and promoting multi-arch `docker buildx build --platform linux/amd64,linux/arm64 --push -t myapp:1.4 .` builds each platform and pushes an index. Note `--push`: a multi-platform result cannot be loaded into the classic local image store, which only models one platform, so `--load` fails. That local-store limitation is the same root cause as the `docker tag` + `docker push` promotion problem — the daemon holds only one platform's manifest, so re-tagging locally publishes a single-platform image and silently drops the others. Registry-native tools avoid this entirely. `docker buildx imagetools create -t myapp:1.4.2 myapp@sha256:<index>` re-tags the index server-side. It can also *assemble* an index from separately built per-platform images, which is the standard pattern when different architectures are built on different machines: `docker buildx imagetools create -t myapp:1.4 myapp:1.4-amd64 myapp:1.4-arm64` ## Consequences for verification and policy Because the index digest and the per-platform digests differ, anything bound to a digest must be explicit about which one. Signatures and attestations are typically attached to the index digest for the released artifact, and sometimes additionally per platform. A policy that verifies "the running image's digest is signed" must resolve the same level the signature was made at, or verification fails for confusing reasons — the image is genuine, but you are checking a different digest of it. Similarly, when a node reports the digest of what it is running, it may report the platform manifest digest it actually pulled rather than the index digest you deployed. Reconciling "deployed digest" with "running digest" therefore needs a resolution step through the index, not string equality. Knowing that this asymmetry exists is what separates a candidate who has operated a mixed-architecture fleet from one who has only read about manifest lists.

  • Why does `docker buildx build --platform linux/amd64,linux/arm64 --load` fail?
    The classic local image store models a single platform per image reference, so it has nowhere to keep an index with several platform manifests. `--load` therefore only works for a single-platform build. Push to a registry instead, or enable the containerd image store, which can hold multi-platform images locally.
  • A cluster reports a running image digest that does not match the digest recorded at deploy time, yet nothing was redeployed. What is the benign explanation?
    The deployment recorded the index digest while the node reports the per-platform manifest digest it actually pulled. Both are correct and immutable; they simply address different levels of the same artifact. Reconciliation must resolve the index to its platform entries rather than compare strings, and this is also why signature verification has to target the level the signature was made at.

The index is a table of contents listing one chapter per architecture; pinning the table of contents keeps every reader on the right chapter, pinning a chapter forces everyone to read the same one.

saying these in an interview costs you the question

  • Believing a tag always points directly at a single manifest
  • Harvesting the pin digest from `docker inspect` on the local host and shipping a platform-locked reference
  • Assuming the index digest and the amd64 manifest digest are the same value
  • Thinking a multi-platform image contains all architectures' layers in one manifest
  • Expecting `--load` to work for a multi-platform buildx build

context