Docker from First PrinciplesPost 3 of 18 · 18 of 18 written

3. Images: layers, hashes and registries

Docker · 3 of 183 min readOct 2026

An image is an ordered stack of read-only filesystem layers plus a small JSON config, every piece addressed by its SHA-256 hash. A container adds one thin writable layer on top, and a union filesystem (OverlayFS) presents the whole stack as a single directory tree.

Layers

Each Dockerfile instruction that changes files (RUN, COPY, ADD) produces a layer: an archive of the files that instruction added, changed or deleted. Deleting is recorded as a special “whiteout” marker that hides the file in the merged view; the original bytes stay in the earlier layer.

That has a sharp consequence. If one RUN downloads a 4 GB checkpoint and a later RUN deletes it, the image still contains 4 GB, because the first layer is immutable. Download and delete in the same RUN, or use a multi-stage build (Part 6) so the file never reaches the final image.

OverlayFS: how layers become one filesystem

OverlayFS stacks directories: the image layers are the read-only “lower” directories, the container’s writable layer is the “upper” directory, and the container sees the “merged” result. Reading a file looks it up from the top down. Writing uses copy-on-write: the first time a container modifies a file from a lower layer, the whole file is copied into the upper layer first.

Copy-on-write is why you never write datasets, checkpoints or databases into the container’s own filesystem. Appending one byte to a 5 GB file from the image copies all 5 GB, the writable layer is slower than a plain disk, and it is deleted with the container. Those belong in volumes or bind mounts (Part 7).

Content addressing

Every layer, config and manifest is named by the SHA-256 hash of its content. Identical content means an identical hash, so it is stored and downloaded once. Ten of your images built FROM the same PyTorch base share those base layers on disk and on the network; only your top layers differ.

Three objects tie it together:

  1. The config holds metadata: environment, entrypoint, working directory, user, the list of layer hashes. Its hash is the image ID.
  2. The manifest lists the config and the compressed layers for one platform, such as linux/amd64.
  3. The image index (also called a manifest list) maps platforms to manifests. That’s how docker pull python:3.12 gives your M1 an arm64 image and a cloud server an amd64 one from the same name.

Tags versus digests

A reference looks like registry/namespace/repository:tag@digest, for example ghcr.io/ulvi/rag-api:1.4.0 or nvcr.io/nvidia/pytorch:<release>-py3. When the registry is omitted Docker assumes docker.io, and official images live under the namespace library.

A tag is a mutable pointer: whoever publishes python:3.12-slim moves it to a new build with every patch release. latest is only the default tag name, not a promise of newest. A digest (@sha256:…) names exact bytes and can never change. Pin base images by digest when reproducibility matters: FROM python:3.12-slim@sha256:<digest>.

The OCI standards

The Open Container Initiative defines three specifications: the image format, the runtime behaviour, and the registry protocol. Because Docker images are OCI images, the same image runs under Docker, Podman, containerd, Kubernetes, and (after conversion) Apptainer on HPC clusters. Today “Docker image” and “OCI image” mean the same thing in practice.

Registries

A registry stores images and serves them over HTTPS. Pulling fetches the index, picks the manifest for your platform, then downloads only the layers you don’t already have. Common ones for AI work are Docker Hub, GitHub Container Registry (GHCR), cloud registries (AWS ECR, Google Artifact Registry, Azure ACR) and NVIDIA NGC (nvcr.io) for GPU-optimised framework images. Docker Hub limits anonymous pulls, so log in on CI machines.

Commands to inspect images

  • docker image ls lists local images and their sizes.
  • docker history <image> shows each layer, the instruction that made it, and its size; it’s the fastest way to find what bloated an image.
  • docker image inspect <image> prints the config: env, entrypoint, layers, architecture.
  • docker buildx imagetools inspect <reference> shows a remote image’s index and which platforms it supports.
  • dive <image> (a third-party tool) lets you browse each layer’s files interactively.