IDs and Digests

Image IDs, Digests and Content-Addressable Storage

Every blob in an image, whether a layer, the config, a manifest or the index, is stored and fetched under the SHA-256 hash of its own bytes. This is content-addressable storage: the name is a checksum, so any change produces a new name, identical content is stored once however many images share it, and a client can verify everything it downloads. You can check the claim with nothing more than sha256sum:

A digest is the SHA-256 of the document it namesShell
docker image inspect alpine:3 --format '{{.Id}} {{json .RepoDigests}}' | cut -c 1-90
docker buildx imagetools inspect --raw alpine:3 | sha256sum
M=$(docker buildx imagetools inspect --raw alpine:3 | jq -r '.manifests[0].digest'); echo $M
docker buildx imagetools inspect --raw "alpine:3@$M" | sha256sum
Output
sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 ["alpine@sha256:29
294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6  -
sha256:d56c381f961d307a21b3ca004cf1e3910f106644aefb1f43e654c8a56c4fd395
d56c381f961d307a21b3ca004cf1e3910f106644aefb1f43e654c8a56c4fd395  -

Hashing the index document reproduces alpine:3's digest exactly, and hashing the amd64 manifest reproduces the digest the index lists for it. The terms are easy to mix up:

The four kinds of image identifier
Term Hash of Where you see it
Digest (repo digest) The index or manifest docker pull, RepoDigests, @sha256: references
Image ID Index or manifest (containerd 234,762 store); config (older store) docker image ls
Layer digest The compressed layer tarball Manifests, registry URLs
Diff ID The uncompressed layer tarball RootFS.Layers in the config

With Engine 29's containerd image store, the image ID of a pulled image is the digest of what you pulled, which is why alpine:3's ID and repo digest match. The classic store used the config's digest instead, so older articles show IDs that never match a registry.