The FROM instruction is the most consequential line in your Dockerfile. It sets the floor for everything that follows — the minimum size your image can ever be, the set of packages it ships with, and the number of CVEs it inherits from day one. Choosing well here is the highest-return, lowest-effort optimisation available.
Base images exist on a spectrum from absolutely minimal to full-featured. Understanding where each option sits helps you choose the right trade-off for your workload.
scratch — Zero bytesFROM scratch is a special Docker keyword that means “start with nothing”. The resulting image contains only what you explicitly copy into it. There are no utilities, no shell, no package manager, no libc.
This is the ideal choice for statically compiled binaries — Go applications in particular. The runtime image is literally your compiled executable and nothing else.
FROM scratch
COPY myapp /myapp
ENTRYPOINT ["/myapp"]
Size: As small as your binary. A typical Go web server: 10–20 MB.
Limitation: No shell means no docker exec debugging. No libc means dynamically linked binaries won’t run. Unsuitable for most interpreted runtimes.
Google’s distroless images contain a language runtime (Python, Node, Java) and their dependencies but no shell (bash, sh), no package manager, and no general-purpose utilities. They are built for production runtime use only.
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /opt/venv /opt/venv
COPY app /app
CMD ["app/main.py"]
Size: Python distroless: ~50 MB. Java distroless: ~130 MB.
Limitation: No shell makes interactive debugging impossible and some init/entrypoint scripts will fail. Build tooling must be in a separate stage.
Alpine Linux uses musl libc instead of glibc and busybox instead of GNU coreutils, resulting in a base image of roughly 5 MB. The apk package manager is fast and the package ecosystem is large.
Many official Docker Hub images offer Alpine variants: python:3.12-alpine, node:20-alpine, golang:1.22-alpine.
Size: Base Alpine: ~5 MB. Python on Alpine: ~50–80 MB.
Critical caveat: Python binary wheels on PyPI are compiled for glibc (manylinux). On Alpine (musl), packages with C extensions either fail to install or must be compiled from source — which requires installing build tools and negates much of the size advantage. This is discussed in detail in Chapter 7.
Official images offer slim variants (e.g., python:3.12-slim-bookworm, node:20-slim) that strip out documentation, man pages, locale data, and packages not needed at runtime. They retain glibc, making them fully compatible with pre-built binary wheels.
Size: python:3.12-slim-bookworm: ~45 MB base. Your final image with dependencies: 80–150 MB.
Best for: Python and Node applications with C extension dependencies; teams that want compatibility without the Alpine musl friction.
Images like python:3.12, node:20, ubuntu:22.04 are designed for development and build environments. They include compilers, build tools, documentation, and hundreds of packages you will never use in production.
Size: python:3.12: ~1 GB. node:20: ~1.1 GB.
Use only as build stages, never as the final runtime image.
| Base image | Approx. size | glibc | Shell | Debug ease | CVE surface |
|---|---|---|---|---|---|
scratch |
0 MB | No | No | Very hard | Minimal |
distroless |
5–130 MB | Yes | No | Hard | Very low |
alpine |
5 MB | No (musl) | Yes (busybox) | Moderate | Low |
slim (Debian) |
30–80 MB | Yes | Yes | Easy | Low–Medium |
| Full (Debian) | 300–900 MB | Yes | Yes | Easy | High |
Using python:3.12-slim-bookworm is better than python:3.12-slim, because the Debian codename (bookworm) anchors you to a specific OS version. But a tag can still be updated by the maintainer with new package versions that introduce incompatibilities or new CVEs.
For maximum reproducibility, pin to a digest:
FROM python:3.12-slim-bookworm@sha256:a1b2c3d4...
The SHA256 digest uniquely identifies a specific image manifest. It will never change. The trade-off is that you must update it explicitly when you want upstream security patches — which is actually a feature for teams that want predictable, auditable builds.
docker pull python:3.12-slim-bookworm
docker image inspect python:3.12-slim-bookworm \
--format ''
Before switching, audit what you have:
# See how many CVEs your current base image has
docker scout cves python:3.12
# Get a recommendation for a better base image
docker scout recommendations python:3.12
docker scout requires a Docker Hub account for full results but the free tier covers most use cases. The output typically shows the current CVE count and a recommended alternative with a reduced count.
Use this decision tree:
FROM scratch (or alpine if you need a shell for scripts)eclipse-temurin:21-jre-alpine or gcr.io/distroless/java21-debian12python:3.12-slim-bookwormpython:3.12-alpine or python:3.12-slim-bookwormnode:20-alpineFROM instruction sets the minimum size floor and inherits all CVEs from the base image.slim or alpine variants of official images over their full counterparts.docker scout to audit CVE counts.| ← Chapter 1: Understanding Docker Image Layers | Table of Contents | Chapter 3: Multi-Stage Builds → |