What is it? #
An image is a read-only template containing a filesystem and the metadata needed to start a container from it.
Images are built in layers. Each instruction in a Dockerfile produces a layer, layers are cached, and images sharing a base share those layers on disk and over the network.
Tags name a version of an image. latest is not a special version — it is just the default tag, and relying on it means you do not know what you are running.
Size matters more than people expect. A 1.2 GB image is slower to build, slower to pull, slower to deploy and contains more software that could be vulnerable.
Think of it like this #
A recipe that produces a sealed meal kit. The kit is identical every time it is made from the same recipe, and kits sharing ingredients store them once.
The label on the kit is the tag. A label reading "newest" tells you nothing about what is inside.
Simple example #
Two applications both based on python:3.12-slim. The base layers are stored once, and each image adds only its own dependencies and code on top.
Code #
docker pull python:3.12-slim
docker images # what is stored locally, with sizes
docker history python:3.12-slim # the layers and what created them
docker inspect python:3.12-slim # full metadata
docker rmi <image> # remove one
docker image prune -a # remove everything unused (reclaims a lot)
Layers and caching
FROM python:3.12-slim ← layer 1 (shared with every other image using it)
WORKDIR /app ← metadata, no real layer
COPY requirements.txt . ← layer 2 (changes only when requirements change)
RUN pip install -r requirements.txt ← layer 3 (expensive; cached if layer 2 is unchanged)
COPY . . ← layer 4 (changes on every code edit)
Docker rebuilds from the first changed layer onwards. Copying
requirements before the code means a code change does not reinstall
dependencies — a rebuild of seconds instead of minutes.
Tags
python:3.12.7-slim-bookworm exact: reproducible, what production should use
python:3.12-slim minor updates allowed: reasonable for development
python:3-slim any 3.x: too loose
python:latest whatever was pushed last: not a version at all
Digests are absolute:
python@sha256:9c1c... this exact image, forever
Size, for the same application
python:3.12 ~1.0 GB full distribution, compilers included
python:3.12-slim ~150 MB minimal Debian, the usual choice
python:3.12-alpine ~60 MB musl libc: smaller, but some wheels
must compile from source, and subtle
differences can appear at runtime
distroless ~50 MB no shell, no package manager: most secure,
hardest to debug
Slim is the pragmatic default. Alpine is worth it when size genuinely
matters and you have tested your dependencies against it.
How it works #
Each layer is a set of filesystem changes. Layers are immutable and content-addressed, so two images built from the same base reference the same stored layers rather than duplicating them.
The build cache works layer by layer. If an instruction and its inputs are unchanged, the cached layer is reused; once one layer changes, every layer after it is rebuilt.
That is why instruction order matters so much. Copying dependency files and installing before copying source code means everyday code changes reuse the expensive install layer.
Layers are additive only. Deleting a file in a later layer hides it but does not remove it from the image, which is why secrets copied in and later deleted are still recoverable from the image history.
Tags are mutable pointers. The same tag can point at a different image tomorrow, which is precisely why production should pin an exact version or a digest.
Smaller base images contain fewer packages, which means a smaller attack surface and fewer vulnerabilities reported by scanners, as well as faster pulls.
Alpine's smaller size comes from musl libc rather than glibc. Most software works, some Python wheels must be compiled instead of downloaded, and occasional runtime differences appear — which is why testing it rather than assuming is the right approach.
Real-world use #
Registries store images: Docker Hub, GitHub Container Registry, and cloud provider registries. Private registries hold your own application images.
Image size affects deployment speed directly. Pulling 1 GB to five machines on every deploy is slow; pulling 100 MB is not.
Vulnerability scanning is standard in CI. A minimal base image produces dramatically shorter reports, mostly because it contains less software.
Pinning by digest is the strongest form of reproducibility, and it is what supply-chain-conscious teams use for base images.
The layer caching behaviour is the single biggest factor in build speed, which the Dockerfile lesson covers in practical detail.
Common mistakes #
- Using the
latesttag in production, so nobody knows what is deployed. - Copying source code before installing dependencies, destroying the cache.
- Assuming a deleted file is removed from the image — earlier layers still contain it.
- Choosing Alpine without testing, then debugging obscure library differences.
- Never pruning unused images until the disk is full.
Practice #
Pull the full and slim variants of a language image and compare their sizes. Inspect the layer history of one. Then build a small image twice, changing only source code, and observe which layers are rebuilt and which come from cache.