`docker images` shows an IMAGE ID starting with sha256, and `docker inspect` also reports a RepoDigests entry like repo@sha256:.... Why are those two sha256 values different, and which one identifies what you pulled from the registry?
answer
- image ID = sha256(config blob)
- repo digest = sha256(manifest)
- config holds diff_ids; manifest holds compressed layer digests
- built image: ID yes, RepoDigest no
- multi-arch: one index digest, many image IDs
basics
~20 sThe image ID is the sha256 of the image's local config JSON blob. The repo digest is the sha256 of the manifest the registry served. The manifest points at the config plus layers, so the two hash different documents. Registry references use the repo digest.
solid answer
~50 sThey hash two different documents in the same content-addressed chain. - **Image ID** = digest of the **config blob** — the JSON holding architecture/OS, Env, Cmd, Entrypoint, User, Labels, and `rootfs.diff_ids` (the uncompressed layer hashes). It is what the local daemon uses to identify an image. - **RepoDigest** = digest of the **manifest** the registry stored — the JSON that lists the config descriptor plus the compressed layer descriptors, with media types and sizes. Since the manifest contains the config's digest and its own layer digests, it hashes to something different from the config alone. Consequences: a locally built image has an image ID but no RepoDigest until it is pushed. Retagging never changes the image ID. And for a multi-arch reference, the RepoDigest you pulled may be the *index* digest, while the image ID corresponds only to the platform-specific image you materialised. Pin deployments by the repo digest — that is the name the registry understands.
code
bash · 6 linesdocker image inspect --format 'ID={{.Id}}' alpine:3.20
docker image inspect --format 'RepoDigests={{.RepoDigests}}' alpine:3.20
docker image inspect --format 'diff_ids={{.RootFS.Layers}}' alpine:3.20
# what the registry actually stores under the tag
docker buildx imagetools inspect --raw alpine:3.20go deeper
Know that both are sha256 values but of different things, and that the registry-resolvable one is the repo digest.
Name the documents precisely: config blob versus manifest, and explain that the config's diff_ids are uncompressed layer hashes while the manifest lists compressed blob digests.
Add the operational consequences — pin by repo digest, expect empty RepoDigests before push, and expect one index digest to accompany different image IDs per architecture.
Discuss which identifier your platform standardises on for admission control, SBOM/scan correlation and signature verification, and why registry-resolvable identity is the only workable contract across clusters.
## The chain of documents An OCI/Docker image is not one file. It is a small tree of content-addressed documents: 1. **Layer blobs** — each a gzip-compressed tar of filesystem changes. 2. **Config blob** — one JSON document describing how to run the image (architecture, os, Env, Cmd, Entrypoint, WorkingDir, User, ExposedPorts, Labels), plus `rootfs.diff_ids` listing the *uncompressed* hash of each layer, plus a `history` array. 3. **Manifest** — one JSON document listing the config blob and the layer blobs as descriptors: `{mediaType, digest, size}` each. 4. Optionally an **index** (manifest list) pointing at several manifests, one per platform. Every arrow in that tree is a sha256 digest, which is why the whole thing is a Merkle tree. ## Image ID = digest of the config Since Docker's schema 2 format, the local image ID is literally `sha256(config blob bytes)`. That is why it still transitively identifies the content: the config lists `diff_ids` for every layer, so changing any layer changes a diff_id, which changes the config bytes, which changes the image ID. Properties that follow: - Building the same Dockerfile twice with identical inputs *can* yield the same image ID, but timestamps and non-reproducible steps usually make it differ. - `docker tag` adds a name; it never changes the image ID. - A freshly built image has an image ID and no RepoDigest at all — there is no registry manifest yet. - The image ID is stable regardless of which registry you pulled from. ## RepoDigest = digest of the manifest When the daemon pulls, the registry returns the manifest and (per the distribution spec) a `Docker-Content-Digest` header. The client hashes the received bytes, checks them, and records `repository@sha256:…` in `RepoDigests`. This is the only value the registry can resolve: `docker pull repo@sha256:…` is a manifest lookup by digest. The manifest digest differs from the image ID because the manifest is a different document — it includes media types, sizes, and the digests of the *compressed* layer blobs, whereas the config lists *uncompressed* diff_ids. Two registries could even store manifests whose bytes differ (different media types or ordering) for the same underlying config, producing two repo digests for one image ID. ## Multi-arch wrinkle For a multi-platform tag, what a registry serves under the tag is usually an **index**. Pulling records the index digest as the RepoDigest, while the image ID you get is that of the platform-specific config the daemon actually materialised. So on an amd64 host and an arm64 host, the same RepoDigest can accompany two different image IDs — and both are correct. This surprises people who assume the two values map one-to-one. ## Which to use - **Deployments, admission policies, scan records, SBOM references** → repo digest. It is registry-resolvable, portable to other hosts, and the thing signatures are made over. - **Local reasoning, layer caches, debugging "is this the image I just built"** → image ID. It exists before any push and does not depend on a registry. A useful mental check: if the value needs to survive a pull on a machine that has never seen the image, it must be a repo digest. If it only has to make sense on this daemon, the image ID is fine. ## Reading them `docker image inspect` shows `Id`, `RepoTags`, `RepoDigests`, and `RootFS.Layers` (the diff_ids). `docker buildx imagetools inspect --raw repo:tag` prints the raw manifest or index exactly as the registry stores it, which is the fastest way to see whether a tag resolves to an index or a single manifest.
- You built an image locally and `docker inspect` shows an empty RepoDigests array. Is the image broken?No. RepoDigests is populated from a registry interaction — a push or a pull. A locally built image has never had a manifest created and stored by a registry, so there is nothing to record. After `docker push`, the daemon learns the manifest digest and RepoDigests fills in.
- Can the same image ID appear under two different repo digests?Yes. The image ID hashes only the config, while the manifest that wraps it can legitimately differ between registries or tooling — different media types, descriptor ordering, or added annotations produce different manifest bytes and therefore a different manifest digest. The runtime behaviour is identical; only the registry-level name differs.
saying these in an interview costs you the question
- Saying the image ID is the hash of all the layers concatenated
- Assuming the image ID and the repo digest should be equal and that a mismatch means corruption
- Trying to `docker pull` an image by its image ID, which no registry can resolve
- Claiming a locally built image must have a repo digest, or that `docker tag` changes the image ID