An image is not a file. It is not a disk snapshot. It is a manifest — a recipe card that references a stack of content-addressable layers. Understanding this changes how you think about everything Docker does.
1. The Mental Model
Most people think of an image as "a file you download, like an ISO." That model is wrong and will mislead you repeatedly. Here is the correct mental model:
Concretely, when you docker pull nginx, Docker downloads:
- A manifest — a small JSON document (~2 KB)
- A config blob — another JSON document with environment vars, entrypoint, etc.
- Layer blobs — compressed tar archives of filesystem diffs
The manifest is the image. Everything else is referenced by hash.
2. Content-Addressable Storage
Every layer is identified by its SHA256 hash. The name IS the content. If even one byte changes, the hash changes, and it becomes a different layer.
# Two images sharing a layer — the hash is identical:
sha256:a1b2c3d4... ← Debian base layer (75 MB)
nginx → uses sha256:a1b2c3d4... + sha256:e5f6a7b8... + sha256:c9d0e1f2...
node:18 → uses sha256:a1b2c3d4... + sha256:f3a4b5c6... + sha256:d7e8f9a0...
Because both images reference the same Debian base layer hash, Docker stores it once on disk. When you pull the second image, that 75 MB layer is already there — zero network transfer, zero extra disk.
This principle applies at every level — your local disk, the registry, and network transfers between them. A registry serving millions of images deduplicates aggressively using this mechanism.
3. Image Manifest
The manifest is the "recipe card." It's a JSON document conforming to the OCI Image Spec:
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:9e2b3c...config-hash...",
"size": 7023
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:a1b2c3d4...base-layer...",
"size": 32654321
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:e5f6a7b8...app-layer...",
"size": 1547892
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:c9d0e1f2...config-layer...",
"size": 423
}
]
}
Key observations:
- config — points to a JSON blob with env vars, entrypoint, exposed ports, working dir
- layers — an ordered list. Order matters: layer 0 is the base, each subsequent layer overlays the one below
- Everything is referenced by digest (content hash). Change the content → different hash → different image
4. Layers in Detail
Each layer is a tar archive of filesystem changes relative to the layer below. Think of it as a diff — it contains only what was added, modified, or deleted in that build step.
# Layer 1 (base): Debian minimal
/bin/sh
/bin/ls
/usr/lib/...
/etc/passwd
/etc/apt/...
# Layer 2: Install Node.js (adds/modifies files)
/usr/local/bin/node
/usr/local/bin/npm
/usr/local/lib/node_modules/...
# Layer 3: Copy application code
/app/server.js
/app/package.json
# Layer 4: Install dependencies
/app/node_modules/express/...
/app/node_modules/lodash/...
Deletions are represented with special "whiteout" files. If layer 3 removes /tmp/build-cache, the tar contains a file named /tmp/.wh.build-cache — the union filesystem interprets this as "hide that path."
5. How Images Become Containers
When you run docker run nginx, three things happen:
- Layer stacking: The read-only image layers are stacked using a union filesystem (usually
overlay2on Linux). The result looks like a single coherent filesystem. - Writable layer: A thin, empty writable layer is placed on top. All container writes (new files, modifications) go here.
- Process launch: The container's entrypoint process starts inside its namespaces, seeing the merged filesystem.
The image is never modified. Multiple containers can share the same image layers simultaneously — each has its own writable layer on top.
This is why starting a container is nearly instantaneous — there's no copying of the image. It's just a mount operation plus a process fork.
6. Image IDs vs Tags
Two completely different concepts that people constantly confuse:
| Concept | Example | Properties |
|---|---|---|
| Image ID | sha256:9e2b3c4d... |
Content hash. Immutable. If the image changes, the ID changes. |
| Tag | nginx:1.25, nginx:latest |
Human-readable pointer. Mutable! Can be moved to point at a different ID at any time. |
latest is just a tag name. It does NOT automatically mean "the newest version." It's simply the default tag applied when no tag is specified. A maintainer can point latest at any image they choose — or never update it at all.
For reproducible deployments, always reference images by digest:
# Mutable — could change tomorrow:
FROM nginx:1.25
# Immutable — will ALWAYS be exactly this image:
FROM nginx@sha256:9e2b3c4d5e6f7a8b...
7. Inspecting Images
Docker gives you tools to explore everything we've discussed:
# See the layer history (what each layer added)
$ docker history nginx
IMAGE CREATED CREATED BY SIZE
9e2b3c4d5e6f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago COPY conf /etc/nginx # buildkit 2.3kB
<missing> 2 weeks ago RUN /bin/sh -c apt-get update && ... 58MB
<missing> 2 weeks ago ADD debian-slim / # buildkit 74MB
# Deep inspection — full JSON config, layer digests, env vars
$ docker image inspect nginx
# Remote manifest (without pulling)
$ docker manifest inspect nginx:latest
🧪 Hands-On Task 1: Inspect Image Layers
# Pull nginx and examine its structure
docker pull nginx
# View layer history
docker history nginx
# Inspect full metadata (pipe to less or jq)
docker image inspect nginx | head -80
# Look specifically at the layer digests:
docker image inspect nginx --format '{{json .RootFS.Layers}}' | python3 -m json.tool
Questions to answer:
- How many layers does the nginx image have?
- What is the total size vs the largest single layer?
- What environment variables are baked into the image?
🧪 Hands-On Task 2: Prove Containers Don't Share Writes
# Run two containers from the same image
docker run -d --name box1 nginx
docker run -d --name box2 nginx
# Write a file in box1
docker exec box1 sh -c 'echo "hello from box1" > /tmp/proof.txt'
# Verify it exists in box1
docker exec box1 cat /tmp/proof.txt
# Output: hello from box1
# Prove it does NOT exist in box2
docker exec box2 cat /tmp/proof.txt
# Output: cat: /tmp/proof.txt: No such file or directory
# Clean up
docker rm -f box1 box2
This proves that each container has its own writable layer. The shared image layers below are untouched.
🧠 Quiz Time
🌍 Not Just Docker
Buildah can create OCI-compliant images without a Docker daemon running. Podman can pull and run them. Skopeo can inspect and copy them between registries. The image format is an open standard.
📋 Key Takeaways
- An image is a manifest pointing to a config + ordered layers, all referenced by SHA256 hash
- Content-addressable storage means shared layers are stored once — saving disk and bandwidth
- Each layer is a tar of filesystem diffs — additions, modifications, and whiteout deletions
- Containers get a thin writable layer on top of the read-only image stack — the image is never modified
- Multiple containers share image layers simultaneously — containers are cheap
- Tags are mutable pointers; image IDs (digests) are immutable content hashes. Use digests for reproducibility.
docker historyanddocker image inspectlet you explore all of this yourself