Skip to content
Lesson 15 of 19

Step 1 of 5 · Reading · ~3 min

Read

Filesystems & Images

OCI Image Format

"Image" sounds like a file. It is not — an image is a small graph of blobs that all reference each other by the hash of their own contents, and understanding that graph explains image IDs, deduplication, signing and docker pull in one go.

Three kinds of blob

  1. A manifest: JSON naming one config blob and an ordered list of layer blobs.
  2. A config: JSON with the architecture, OS, environment, entrypoint, the layer diffIDs and the build history.
  3. The layers themselves: tar archives, usually gzipped.
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:...",
    "size": 7023
  },
  "layers": [
    { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:...", "size": 32654 }
  ]
}

Every reference in that document is a digest, never a name or a path. That is what "content-addressable" buys: a registry can verify what it served, a client can verify what it received, and two parties can agree on an image without trusting each other's naming.

The cascade

Change one byte of the config — a different ENV, a different entrypoint — and the consequences run upward, mechanically:

config bytes change
  -> config digest changes            (the image ID is the config's digest)
  -> the manifest's config.digest field changes
  -> the manifest's own digest changes
  -> pull-by-digest for the old image still resolves, to the old image

The layers did not change and are not re-uploaded. This is also why docker images can show two IDs for what looks like the same build, and why a tag is not an identity: tags are mutable pointers, digests are not.

digest against diffID

The one genuine subtlety in the format, and a routine interview question:

  • digest — SHA-256 of the compressed blob. It is what the registry stores and what travels on the wire, so the manifest uses it.
  • diffID — SHA-256 of the uncompressed tar. It is what the runtime stacks, so the config uses it.

Two registries may gzip the same layer to different bytes and so publish different digests, while the runtime sees one diffID and stacks one layer. Separating the two lets transfer and stacking each be content-addressed on the bytes that matter to them.

Why the exercise is a simulator

Nothing here is privileged — the exercise is a simulation only in that it drops gzip, so digest and diffID coincide. Everything else is the real construction: hash the content, record the size, keep the layers in order, and canonicalise the config before hashing it.

Your exercise: Build an OCI Manifest

A builder doesYour simulator does
write a layer tar, hash it, record its sizeLAYER <id> <content> — emit sha256:<hex> <size>
accumulate config fieldsCONFIG <key>=<value> ...
append a layer descriptor, in orderADDLAYER <id> — or ERR no such layer
serialise the manifestMANIFEST
ask the registry whether a blob is presentPULL <digest>

Two details decide whether your output matches. Canonicalise before you hash: the config digest is the SHA-256 of key=value pairs joined with ; in lexicographic key order, so CONFIG os=linux arch=amd64 and CONFIG arch=amd64 os=linux must produce the same digest — that is the whole point of a canonical form. And size is bytes, not characters: hash and measure the encoded content, so a multi-byte character counts once for the hash and more than once for the size.

MANIFEST prints the two fixed lines, the config line, then every layer: line in ADDLAYER order, and only then every diffID: line in the same order — two passes over the same list, not one interleaved pass.

Reference: https://github.com/opencontainers/image-spec/blob/main/manifest.md

Up nextRegistry Pull ProtocolFilesystems & Images

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…