How it works
The explanation, with examples you can run.
Looking inside
docker history hello:1 --format '{{.Size}}\t{{.CreatedBy}}'# 0B CMD ["node" "server.js"]# 0B USER node# 12.3kB COPY . . # buildkit# 2.72MB RUN /bin/sh -c npm ci --omit=dev # buildkit# 16.4kB COPY package.json package-lock.json ./ # buildkit# 8.19kB WORKDIR /app# 0B CMD ["node"]# ... (the node:22-alpine base layers)Instructions such as CMD, USER and ENV only change the image config, so they add no file layer. docker image inspect --format '{{json .RootFS.Layers}}' lists the layer digests (sha256:1b34...) that make up the filesystem.
Content addressing
Every piece of an image is named by the SHA-256 hash of its bytes: each layer, the config, and the manifest that lists them. Because the name is derived from the content:
- two images that share a base share those layers on disk and in the registry, and a pull downloads only the layers you do not already have;
- a digest can be verified after download, so tampering is detectable;
- the manifest's own digest identifies the whole image exactly, which is what pinning by digest relies on.
The OCI image specification standardises this layout: an image index (one entry per platform), manifests, configs and layers. Docker, containerd, Podman, and all major registries speak it.
The union filesystem and copy-on-write
At run time the storage layer (overlayfs on most Linux hosts) presents the layers as one merged tree. Reads fall through to the highest layer that has the file. The first write to a file from a lower layer copies it up into the container's writable layer, and the change happens there; the image itself is never modified. That is why a hundred containers from one image start instantly and use almost no extra disk.
Two consequences catch people out:
- Deleting does not shrink. Removing a file in a later layer only records a whiteout that hides it. The bytes are still in the earlier layer and still shipped.
- Container writes are temporary. Everything in the writable layer disappears with
docker rm. Writing heavily there is also slower than writing to a volume.
A recent change: the containerd image store
Docker Engine 29.0 made the containerd image store the default for fresh installations (upgraded daemons keep the classic overlay2 driver until you switch); Docker Desktop already used it. Images are now kept by containerd's content store and unpacked by snapshotters. Day to day the commands are the same, but the store can hold multi-platform images and attestations, and docker image ls reports both disk usage and content (download) size:
docker image ls --tree# IMAGE ID DISK USAGE CONTENT SIZE# hello:1 ba5bcd6124ac 237MB 61.6MBCheck yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.