# Images and Containers

## 1. Recap: The Distinction That Everything Else Builds On

The Docker Fundamentals topic introduced images as read-only templates and containers as running instances of them. This topic goes one level deeper: **how** an image is actually structured on disk, and **what exactly** a container adds on top of it. Understanding this is the foundation for writing efficient Dockerfiles (next topic), reasoning about build cache, and understanding multi-stage builds.

## 2. An Image Is a Stack of Layers

A Docker image is not one big filesystem blob. It's an ordered stack of **layers**, where each layer is a filesystem diff — a record of files added, changed, or deleted relative to the layer below it. Layers are:

- **Immutable** — once created, a layer's content never changes.
- **Content-addressed** — each layer is identified by a cryptographic hash of its contents, not by a name you chose.
- **Shareable** — if two images both have a layer with the identical hash (e.g., both `FROM node:20-alpine`), Docker stores that layer's data only **once** on disk and reuses it for both images.

```bash
docker history node:20-alpine
# IMAGE     CREATED BY                                      SIZE
# <hash>    CMD ["node"]                                     0B
# <hash>    RUN apk add --no-cache ...                        41MB
# <hash>    ENV NODE_VERSION=20.11.0                          0B
# <hash>    /bin/sh -c #(nop)  CMD ["/bin/sh"]                0B
# ...
```

Each row in `docker history` corresponds to one instruction in the Dockerfile that built the image (covered fully in the Dockerfile topic) and, for instructions that touch the filesystem, one layer.

## 3. From Layers to a Single Filesystem: The Union Filesystem

A process running inside a container needs to see one coherent filesystem, not a stack of diffs. This is the job of a **union filesystem** — on Linux, most commonly **OverlayFS**. OverlayFS takes N read-only directories (the image's layers, "lower" dirs) plus one writable directory (the container's own layer, "upper" dir) and presents a single merged view: for any given path, the file from the **topmost** layer that contains it wins, effectively shadowing the same path in any layer below.

![An image's layers merge through a union filesystem into one read-only view; a container adds its own writable layer on top, using copy-on-write for any file it modifies](diagrams/01-layered-filesystem.svg)

## 4. What a Container Actually Adds

When you `docker run` an image, Docker does **not** copy the image's data anywhere. It:

1. Creates a new, empty, writable layer (the "container layer") on top of the image's stack of read-only layers.
2. Creates a new set of namespaces (PID, network, mount, etc. — from the Fundamentals topic) and a cgroup for the new process.
3. Starts the image's configured process (or your override command) inside those namespaces, with its root filesystem mounted as the OverlayFS merge of image layers + the new writable layer.

This is why starting a container is fast — no data copy — and why disk usage for N containers from the same image is proportional to what each container actually **writes**, not to the image size N times over.

## 5. Copy-on-Write

If a containerized process modifies a file that physically lives in one of the read-only image layers, OverlayFS does not modify that layer (it can't — it's read-only and shared). Instead it performs **copy-on-write**: the file is copied up into the container's writable layer in full, and the edit happens there. The original file in the image layer is untouched. This has two direct consequences:

- **The underlying image is never mutated by running a container from it** — you can run the same image a thousand times and each container's edits are invisible to the others and to the image itself.
- **Modifying a large file that lives deep in the image is expensive the first time** — a 1GB file needs a full 1GB copy-up before a single byte can change, even to flip one bit. This is one of several reasons large files that change often (logs, databases) belong in a volume (its own topic), not the container's writable layer.

## 6. Inspecting What's Actually There

```bash
docker inspect myimage:tag          # full metadata: layers (RootFS.Layers), config, env, entrypoint...
docker history myimage:tag          # per-layer size and the command that created it
docker diff mycontainer             # files added(A)/changed(C)/deleted(D) in a running container's writable layer
docker export mycontainer > fs.tar  # flatten a container's merged filesystem into one tarball (loses layer history)
docker save myimage:tag -o img.tar  # save an image WITH its layers intact (for `docker load` elsewhere)
```

`docker export` vs `docker save` is a common point of confusion: `export` gives you a flat filesystem snapshot of a container (useful for inspecting final state, not for redistributing as a layered image), while `save` preserves the full image, including individual layers and metadata, in a format `docker load` can reconstruct exactly.

## 7. Two Containers, One Image, Independent Writes

```bash
docker run -d --name c1 alpine sh -c "echo hello > /data.txt && sleep 3600"
docker run -d --name c2 alpine sh -c "sleep 3600"

docker exec c1 cat /data.txt   # "hello"
docker exec c2 cat /data.txt   # "cat: can't open '/data.txt': No such file or directory"
```

Both containers were started from the exact same `alpine` image layers, but each has its own independent writable layer. `c1`'s write to `/data.txt` exists only in `c1`'s writable layer; `c2` never sees it, and the shared `alpine` base layers remain byte-for-byte identical for both.

## 8. Under the Hood

**Layer identity is a content hash, not a Dockerfile line number.** Docker computes each layer's ID from a hash of its diff content (and build context, for build-time layers). This is what allows `docker pull` to skip layers you already have locally — it checks hashes against your local layer store before downloading — and what allows completely unrelated images to share storage for a common base layer without either image "knowing" about the other.

**The image build cache is layer reuse applied at build time, not run time.** When you `docker build`, each instruction is checked against the cache: if an instruction is byte-identical to one previously run, AND every layer before it in this build also hit the cache, AND (for `COPY`/`ADD`) the referenced files' content hashes match what was copied last time, Docker reuses the existing layer instead of re-executing the instruction. This is exactly why layer **order** in a Dockerfile matters enormously for build speed — covered in full in the Dockerfile topic — but the mechanism underneath is the same content-addressed layer store described here.

**OverlayFS has a hard practical limit worth knowing about: it has (or had, depending on kernel version) a maximum stack depth** (historically 128 layers on older kernels), and very deep layer stacks add lookup overhead — every file access may need to check layers from the top down until a match is found. This is one motivation (a minor one compared to build-cache design) for tools like `docker build --squash` or multi-stage builds (its own topic) that reduce the final layer count.

**A container's writable layer is itself just another OverlayFS "upper" directory** — mechanically no different from an image layer except that it's mutable and gets deleted (along with all its contents) the moment the container is removed. `docker commit` is the escape hatch that turns a container's writable layer into a new permanent, read-only image layer — useful for debugging or ad hoc snapshotting, but not a substitute for reproducible Dockerfile-driven builds because it captures whatever undocumented state happens to be in that container right now.

## 9. Common Pitfalls

1. **Assuming `docker run`ing the same image twice gives you shared, mutable state** — each container gets an independent writable layer; changes in one are invisible to the other and to the image.
2. **Editing files inside a running container as a "fix" and expecting it to persist across `docker run`s of the image** — that edit lives only in the container's writable layer, which is destroyed on `docker rm`; the fix belongs in the Dockerfile (rebuild the image) or in a volume, not in a live container.
3. **Confusing `docker export`/`import` with `docker save`/`load`** — export/import flattens to a single layer and discards image metadata/history; save/load preserves the full layered image losslessly. Using export as a backup mechanism for an image silently throws away its layer structure.
4. **Not realizing a "small" Dockerfile change can invalidate a huge amount of cached layers** — because every layer's cache validity depends on every layer above and below it being unchanged, editing an early instruction cascades: everything after it must rebuild even if those later instructions themselves didn't change (fully explained with ordering strategy in the Dockerfile topic).
5. **Treating `docker commit` as a build process** — it captures ad hoc, undocumented container state into an image; anyone rebuilding "from scratch" gets nothing describing how that image was actually produced, unlike a Dockerfile, which is a reproducible, reviewable recipe.
6. **Copying large mutable files (databases, logs) into the image or writing them into the container's writable layer** — every write triggers copy-on-write overhead if the file originated in a read-only layer, and none of it survives container removal; this data belongs in a volume.

## 10. Quick Reference

```
Image  = ordered stack of immutable, content-addressed, shareable layers
Container = same image layers (read-only) + one new writable layer (upper dir)
             merged into one view via a union filesystem (OverlayFS on Linux)

docker history <image>     per-layer size + originating instruction
docker inspect <image>     full metadata, RootFS.Layers list
docker diff <container>    A/C/D changes in the container's writable layer
docker export <container>  flatten container fs to a tarball (no layer history)
docker save <image>        preserve full layered image to a tarball (for `load`)
docker commit <container>  freeze a container's writable layer into a new image layer
                            (ad hoc — not a substitute for a Dockerfile)

Copy-on-write: editing a file from a read-only layer copies it UP into the
writable layer first; the original layer is never mutated.

Build cache = layer reuse at build time, keyed on instruction + prior cache hits
              + (for COPY/ADD) content hash of the copied files.
```
