Images Are Onions
Layers
Every image you've pulled or run so far (nginx:alpine, for instance) looks from the outside like one self-contained package. Internally, it's not one big file at all - it's a stack of read-only layers, where each layer is a recorded set of filesystem changes ('add these files', 'install this package') produced by one step of building the image:
┌─────────────────────────────┐ ← your app code (layer 4, 2 MB)
│ COPY app/ /app │
├─────────────────────────────┤ ← dependencies (layer 3, 60 MB)
│ RUN pip install -r reqs.txt │
├─────────────────────────────┤ ← runtime (layer 2, 25 MB)
│ RUN apk add python3 │
├─────────────────────────────┤ ← base OS (layer 1, 7 MB)
│ FROM alpine:3.20 │
└─────────────────────────────┘
Why this design is brilliant:
- Sharing - 50 containers from one image share the same layers on disk; each container adds only a thin writable layer
- Caching - rebuild only the layers whose inputs changed (change app code → only layer 4 rebuilds)
- Fast pulls -
docker pullskips layers you already have
$ docker image history nginx:alpine # see every layer + its size
$ docker image inspect -f '{{.Size}}' nginx:alpine
Analogy: Layers are like Git commits for filesystems: each records a diff on top of its parent, identical history is shared, and identical layers are stored exactly once.