Adetayo Akinsanya unkletayo.dev

From Image to Container: Read-Only Layers, Writable Layers, and Execution State

Understanding OverlayFS union mounts, upperdir writable layers, and ephemeral state.

Adetayo Akinsanya (unkletayo) 2026-09-15

Part 9 in Series — Catch up on the previous article: Container Registries: Content-Addressable Storage, Push/Pull Protocols, Tags vs Digests (Part 8) before diving into this post.

You run a database container using a single image:

docker run -d --name db1 -e POSTGRES_PASSWORD=secret postgres:15

Later, you launch a second instance using the exact same image:

docker run -d --name db2 -e POSTGRES_PASSWORD=secret postgres:15

Inside db1, an application creates a new database table and inserts 1,000,000 rows.

When you log into db2 and query the database, the new table does not exist.

Both containers were launched from the exact same 350 Megabyte postgres:15 image on disk. Yet db1 wrote gigabytes of data while db2 remained completely clean.

How does Docker allow multiple running container instances to write isolated files independently without modifying or duplicating the shared 350MB base image on disk?

The secret lies in the transition from a static Image to an active Container using an Ephemeral Writable Layer.


1. Static Image vs Running Container

To understand container execution state, we must distinguish between an Image and a Container:

[ STATIC IMAGE ]                                 [ ACTIVE RUNNING CONTAINER ]
- Immutable set of read-only layers              - Read-Only Image Layers (Shared)
- Content-addressed by SHA-256 digests          + 1 Ephemeral Writable Container Layer
- Stored statically in registry / disk          + Isolated Namespaces (PID, NET, MNT)
- Serves as a template                           + Process Execution State (PID 1)

An Image is a static, immutable blueprint. A Container is a dynamic instantiation of that image template consisting of shared read-only layers + an ephemeral writable layer + running kernel namespaces.


2. The Container Layer Stack

When containerd starts a container from an image, the storage driver (such as OverlayFS) constructs a unified file stack:

+---------------------------------------------------------------+
| Container Writable Layer (read-write)                          |
| Stores newly created files, updates, and whiteouts            |
+---------------------------------------------------------------+
| Image Layer 3 (read-only)                                     |
+---------------------------------------------------------------+
| Image Layer 2 (read-only)                                     |
+---------------------------------------------------------------+
| Image Layer 1 (read-only) [Base OS]                           |
+---------------------------------------------------------------+
  1. Read-Only Image Layers: Formed by the Dockerfile build process. These layers are shared across all containers running on the host system. No container process can ever modify bytes stored in these layers.
  2. Container Writable Layer: A thin, empty read-write layer appended to the top of the layer stack when a container starts. Any file creation, modification, or deletion performed by the container process is isolated inside this single layer.

3. Inspecting Container Disk Modifications (docker diff)

You can inspect the exact file modifications stored inside a container’s writable layer using the docker diff command:

docker diff db1

Output:

C /root
A /root/.bash_history
C /var/lib/postgresql/data
A /var/lib/postgresql/data/base/16384/2601

The Three Diff Action Indicators:

  • A (Added): A brand new file or directory was created inside the container writable layer.
  • C (Changed): An existing file inherited from a read-only lower layer was modified. (OverlayFS executed a Copy-on-Write operation, copying the file into the writable layer before editing).
  • D (Deleted): A file inherited from a read-only lower layer was deleted. (OverlayFS wrote a .wh. whiteout marker in the writable layer to mask it).

4. The Container Lifecycle and State Destruction

What happens to data written inside the container layer when a container changes state?

[ docker run ] ---> Container Created & Started (Writable Layer Allocated)
       |
[ docker stop ] --> Container Stopped (Process exits, Writable Layer PRESERVED)
       |
[ docker start ] -> Container Restarted (Process resumes, Writable Layer INTACT)
       |
[ docker rm ] ----> Container Removed (Process destroyed, Writable Layer ERASED!)

Critical Storage Rule:

  • Stopping and restarting a container (docker stop / docker start) preserves all data written to the container writable layer.
  • Removing a container (docker rm) permanently deletes the container writable layer from disk. Any data written to the writable layer that was not saved to a persistent Docker Volume is permanently lost!

Static Image vs Container State Matrix

Feature / DimensionOCI Container ImageActive Container Instance
MutabilityStrictly Immutable (Read-Only)Mutable (Ephemeral Read-Write Layer)
Storage SharingShared across all containers on hostPrivate to the specific container instance
LifecyclePersists on host until docker rmiDestroyed when container is deleted (docker rm)
Execution ContextStatic files and JSON configurationRunning kernel processes, PID 1, RAM allocations
Copy-on-Write CostZero (Read-only access)Incurs disk I/O penalty when modifying lower files

Summary & Next Steps

Converting a static image into a running container involves layering ephemeral state on top of immutable templates:

  • Image Layers are immutable, read-only tarball diffs shared across containers.
  • The Writable Layer captures all runtime modifications (adds, changes, deletes) made by a container instance.
  • docker diff reveals exact modifications recorded in the upper container layer.
  • docker rm destroys the ephemeral writable layer, reinforcing why stateful data must be stored in persistent volumes.

In the next article, we inspect Container Lifecycle and PID 1 Behavior: Signal Propagation and Process Management.

References & Further Reading

  1. Stevens, W. R., & Rago, S. A. (2013). Advanced Programming in the UNIX Environment (3rd Edition). Addison-Wesley.
  2. Krall, T., et al. (2017). tini: A tiny but valid init for containers. GitHub.
  3. Kerrisk, M. (2010). The Linux Programming Interface (Chapter 26: Monitoring Child Processes). No Starch Press.

Up Next in Series →

Part 10: Container Lifecycle and PID 1 Behavior: Signal Propagation and Process Management

Continue to Part 10 →