Adetayo Akinsanya unkletayo.dev

Anatomy of a Docker Image: Layers, Config JSON, and Manifest Specifications

Understanding content-addressable storage digests, multi-arch manifests, and whiteout files.

Adetayo Akinsanya (unkletayo) 2026-09-01

Part 5 in Series — Catch up on the previous article: Building a Container From Scratch in Linux: unshare, chroot, pivot_root, and cgroups (Part 4) before diving into this post.

A developer modifies a single line of code in a microservice application and pushes a git commit to CI/CD.

The build pipeline triggers docker build.

Instead of uploading a 600 Megabyte archive across the network to the container registry, the build completion log displays:

Layer 1: Using cache (75 MB)
Layer 2: Using cache (250 MB)
Layer 3: Pushed (3.2 MB)
Digest: sha256:4f8e9a0...

The deployment completes in 3 seconds.

How does Docker know that 325 Megabytes of the image remained identical, allowing it to push only a tiny 3.2 Megabyte delta?

To understand image layer efficiency, content-addressable storage, and build speed optimization, we must dissect the internal filesystem anatomy of a Docker image.


1. High-Level Image Anatomy

A Docker image is not a single monolithic binary or virtual machine disk image.

It is a content-addressed bundle comprising three main components:

+-------------------------------------------------------------------+
| 1. Manifest List / Index                                          |
|    Selects platform architecture (e.g., linux/amd64 vs linux/arm64)|
+-------------------------------------------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
| 2. Image Manifest JSON                                            |
|    Maps Image Config SHA to ordered list of Layer Diff SHAs       |
+-------------------------------------------------------------------+
                                  |
            +---------------------+---------------------+
            |                                           |
            v                                           v
+-----------------------+                   +-----------------------+
| 3. Image Config JSON  |                   | 4. Layer Diffs        |
|    Env, Cmd, Entrypt  |                   |    tar.gz filesystem  |
|    rootfs diff_ids    |                   |    deltas             |
+-----------------------+                   +-----------------------+

2. Multi-Architecture Manifest Lists

When you execute docker pull nginx:latest on an Intel x86 server and an Apple Silicon M2 laptop, both commands download an image tagged nginx:latest.

However, the x86 host receives x86_64 binaries, while the ARM host receives aarch64 binaries.

This is made possible by the Manifest List (Image Index).

When a client queries a registry tag, the registry returns a Manifest List mapping architectures to specific digest manifests:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:1a2b3c4d...",
      "platform": { "architecture": "amd64", "os": "linux" }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:9f8e7d6c...",
      "platform": { "architecture": "arm64", "os": "linux" }
    }
  ]
}

The Docker engine inspects its local host architecture (amd64), selects matching manifest sha256:1a2b3c4d..., and downloads only the relevant layers.


3. Tarball Layer Diffs and Content Addressing

An image Layer is a compressed tarball (tar.gz) containing filesystem additions, updates, or deletions relative to the parent layer below it.

Layer 3 Tarball: /app/server.js (Updated application code)
Layer 2 Tarball: /usr/bin/node (Node.js runtime environment)
Layer 1 Tarball: /bin/bash, /lib, /etc (Alpine OS rootfs base)

Every layer is identified by a Cryptographic SHA-256 Digest computed over its compressed byte payload.

Content-Addressable Storage (CAS)

Because layers are indexed strictly by SHA-256 digests (sha256:a1b2c3...):

  • If 50 different microservices use node:18-alpine as their base image, the host stores only ONE copy of the Node.js base layers on disk.
  • Pushing or pulling images checks if a layer digest already exists in storage. If matched, network transmission is skipped entirely.

4. The Whiteout Deletion File Mechanism

What happens when a Dockerfile instruction deletes a file created by an earlier layer?

Consider this Dockerfile:

FROM alpine:3.18
RUN fallocate -l 100M /large_file.dat  # Layer 1: Adds 100MB file
RUN rm /large_file.dat                  # Layer 2: Deletes file

What is the total size of the resulting image?

Engineers often assume Layer 2 reduces image size back to 5MB. It does not. The image size is 105MB!

Why Deletions Do Not Shrink Images

Image layers are immutable. Once Layer 1 is written and hashed, its 100MB payload can never be modified or removed from disk storage.

When Layer 2 executes rm /large_file.dat, the storage driver creates a special marker file inside Layer 2’s tarball called a Whiteout File:

Layer 1 (Read-Only):  /large_file.dat (100 MB binary data)
Layer 2 (Read-Only):  /.wh.large_file.dat (0 byte whiteout marker)

When OverlayFS mounts Layer 1 and Layer 2 together, the presence of /.wh.large_file.dat in the upper layer hides /large_file.dat from user-space processes.

However, during docker pull, both Layer 1 (100MB) and Layer 2 (whiteout marker) must still be downloaded across the network!

Best Practice: Delete temporary build artifacts (like package caches or tarballs) inside the exact same RUN instruction where they were downloaded!

# GOOD: Deletes temporary files in the SAME layer before commit
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*

Summary & Next Steps

Docker images use content-addressable layer architectures for storage efficiency:

  • Manifest Lists map target CPU architectures to platform-specific image manifests.
  • Image Manifests tie runtime configurations to an ordered array of SHA-256 layer digests.
  • Layers are immutable tarball diffs stored using content-addressable digests.
  • Whiteout Files (.wh.filename) mask deleted files in upper layers without removing bytes from lower read-only layers.

In the next article, we inspect Dockerfile Instructions and Layer Mechanics: How Build Caching Works Under the Hood.

References & Further Reading

  1. Heo, T. (2015). Control Group v2 Documentation (cgroup2.rst). Linux Kernel Docs.
  2. Gregg, B. (2020). Systems Performance: Enterprise and the Cloud (2nd Edition) — Virtualization & Containers. Addison-Wesley.
  3. Heo, T. (2015). cgroup v2: The New Unified Control Group Hierarchy. Linux Kernel Summit.

Up Next in Series →

Part 6: Dockerfile Instructions and Layer Mechanics: How Build Caching Works Under the Hood

Continue to Part 6 →