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:

$ 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.