Manifests, Digests, and the Registry View
Underneath the friendly tag, an image is identified by its content. A manifest is a small JSON document that lists the image's config blob and the digest of every layer; the image digest is the SHA-256 hash of that manifest. Because the digest is computed from content, the same bytes always produce the same digest, and image@sha256:… names one exact, immutable image no matter what any tag claims.
This is the layer of Docker that turns "what did we ship" from a guess into a fact. A tag can move; a digest cannot. Understanding the manifest, the content-addressing beneath it, and the multi-arch manifest list on top of it is what lets you pin a deploy to exact bytes and explain why the same tag served different layers to your laptop and your server.
The Manifest
A manifest is a JSON document defined by the OCI image spec. It references the image's config blob — the JSON that holds the default command, environment, and other metadata — and lists each layer by its digest and size. Nothing in the manifest is the image's actual data; it is an index of pointers to content stored separately as blobs.
Pulling an image means fetching its manifest first, reading the list of blobs it names, and then downloading only the blobs you don't already have. You can read a manifest without pulling the image at all with docker manifest inspect, which is the cheapest way to see what a tag actually points at.
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 7423,
"digest": "sha256:4f8e3a...d91c"
},
"layers": [
{ "mediaType": "...layer.v1.tar+gzip", "size": 29150924, "digest": "sha256:60a0e9...b3a1" },
{ "mediaType": "...layer.v1.tar+gzip", "size": 7340032, "digest": "sha256:1c8f02...77de" },
{ "mediaType": "...layer.v1.tar+gzip", "size": 153, "digest": "sha256:9b1d4a...0e22" }
]
}
Content-Addressed Layers
Each layer and the config blob are stored and named by their SHA-256 digest — the hash is the address. That gives you deduplication for free: two images that contain an identical layer reference the same digest, so it is stored and transferred once. It also gives you integrity for free: a corrupted or tampered layer hashes to a different value than its digest, and the daemon rejects it on arrival.
Content-addressing is why you don't have to trust the transport to trust the image. The registry could serve a layer over plain HTTP and a man-in-the-middle could swap it, and the pull would still fail — because the bytes wouldn't hash to the digest the manifest demanded.
The Image Digest
Hashing the manifest itself yields the image's digest — the single value that uniquely and permanently identifies the whole image. Request postgres:16@sha256:abc… and the daemon fetches exactly that manifest and exactly the layers it names, regardless of what the 16 tag points at today. The digest is how you pin an immutable image while still writing a readable name.
The daemon prints this digest on every pull, and it is worth reading rather than ignoring. The tag tells a human which image you meant; the digest tells a machine which bytes you got — and the digest is the only one of the two that can be checked against what actually ran.
postgres:16 — a pointer a push can move@sha256:…$ docker pull postgres:16 16: Pulling from library/postgres 60a0e9b3a1d2: Pull complete 1c8f0277de44: Pull complete 9b1d4a0e2233: Pull complete Digest: sha256:9c8f2e1b4a7d6c3f0e5a2b8d1c4f7a9e0b3d6c2f5a8e1b4d7c0f3a6e9b2d5c8f Status: Downloaded newer image for postgres:16 docker.io/library/postgres:16
The Multi-Arch Manifest List
A tag like postgres:16 usually does not point at a single manifest. It points at a manifest list — an image index — that maps each platform to a platform-specific manifest: one entry for linux/amd64, one for linux/arm64, and so on. When you pull, the daemon reads the list, picks the entry matching your host's architecture, and fetches that manifest's layers.
This is why the same tag delivers different layers to an Apple-silicon laptop and an x86 server, and why the digest your laptop records differs from the one your server records under one tag — they resolved to different per-platform images by design. The full treatment of building multi-arch images lands in the CI chapter; here it explains the discrepancy.
Tags Are Just Pointers
A tag is a mutable label the registry maps to a manifest digest — nothing more. Re-push the same tag with new content and the registry simply moves the pointer to the new manifest; the old digest still exists and still names the old bytes, but the tag now resolves somewhere else. Anyone who pulls the tag afterward gets the new content under the unchanged name.
That movability is convenient for humans and dangerous for reproducibility, and it is the entire mechanism behind the :latest problem the next topic takes apart. The digest is the fixed point; the tag is the sticky note someone can peel off and move.
- Believing a tag identifies a fixed image — a tag is a movable pointer, and the same tag can resolve to different content tomorrow, which is exactly why reproducible deploys pin by digest instead.
- Ignoring the digest the daemon prints on pull — that
sha256:…value is the only thing that uniquely and permanently identifies what you actually ran, and it's gone if you don't capture it. - Assuming
docker pull image:tagon two machines gets identical bytes — it does only if the tag hasn't moved between the two pulls; the digest is the guarantee, the tag is not. - Being confused that one tag serves different layers on an ARM laptop and an x86 server — the manifest list resolved to different per-platform images, which is the design, not a bug.
- Pin production images by digest (
@sha256:…), not just by tag, so a moved tag can never silently change what runs across the fleet. - Record the resolved digest of every deployed image, since it is the only durable identifier of what shipped and the only thing an incident review can check against.
- Use tags for human-friendly versioning and digests for machine-enforced immutability — both, for different jobs, rather than choosing one.
- Trust content-addressing for integrity — a layer that doesn't match its digest is rejected on arrival — instead of assuming the transport secured the bytes.
Knowledge Check
What does an image manifest contain, and what does the image digest hash?
- The manifest is JSON listing the config and each layer by digest; the image digest is the SHA-256 of that manifest
- The manifest holds all of the actual layer data inline, and the digest hashes those layers concatenated together in order
- The manifest stores the image's human-readable tag, and the digest is simply the hash of that tag string
- The manifest embeds the original Dockerfile, and the digest is computed by hashing those build instructions
What is the difference between a tag and a digest as image identifiers?
- A tag is a mutable pointer that can be moved; a digest is a content hash that always names the same bytes
- A digest is a mutable label that can be freely reassigned, while a tag is permanently fixed to one immutable image
- A tag physically carries the image's layers with it, while a digest carries only the lightweight metadata
- A tag identifies the target CPU architecture, while a digest identifies the operating system the image runs on
Why does pinning a deploy by digest give reproducibility that pinning by tag does not?
- The digest is derived from content and cannot be moved, so a redeploy always fetches byte-identical content
- Pulling by digest is significantly faster on the wire, so the deploy reliably finishes before the tag can be changed
- A digest compresses the image far more tightly, so there is less data and less that can go wrong during transfer
- A digest encrypts every layer end to end, so the registry cannot alter the content between redeploys
Why does one tag serve different layers to an ARM laptop and an x86 server?
- The tag points at a manifest list that maps each platform to its own manifest, and the daemon picks the matching one
- The registry recompiles the image from source for each requesting architecture at pull time, on demand
- A single shared digest automatically rewrites itself to fit whichever CPU architecture happens to pull it
- It is a transient client-side caching quirk that disappears for good once both machines have pulled the image at least once
You got correct