The base image (FROM …) affects your image’s size, compatibility, security exposure, and how easy it is to debug. The most common choices are Alpine, Debian or Ubuntu “slim” variants, and distroless. None is best for everything. Here’s how to choose.
At a glance
| Base | Approx. size | C library | Shell & package manager | Good for |
|---|---|---|---|---|
| alpine | ~5–8 MB | musl | sh, apk |
Small images; static binaries; simple services |
| debian:bookworm-slim | ~25–30 MB | glibc | bash, apt |
Most apps; best compatibility |
| ubuntu | ~30 MB | glibc | bash, apt |
Teams standardised on Ubuntu |
| distroless (gcr.io/distroless) | ~2–20 MB | glibc (or none) | None | Production images with minimal attack surface |
| scratch | 0 MB | None | None | Fully static binaries (Go, Rust) |
Language images (python:3.12-slim, node:22-alpine) |
Varies | Depends on variant | Yes | Most app builds |
Alpine: small, but musl matters
Alpine uses musl libc instead of glibc. Most software works, but:
- Python: many packages ship prebuilt wheels for glibc (
manylinux). On Alpine, pip may compile from source (slow builds, needs compilers), unlessmusllinuxwheels exist. - Node.js native modules, and some binaries (Oracle clients, some monitoring agents), assume glibc.
- DNS resolution and threading behave slightly differently, which occasionally causes hard-to-debug issues.
Alpine shines for Go and Rust binaries, Nginx, Redis, and simple utilities.
Debian slim: the safe default
-slim variants strip documentation and extras, but keep glibc and apt. They have the widest compatibility, and familiar tools for debugging. For most Python, Node, PHP and Java apps, *-slim (Debian bookworm) is the pragmatic default.
Distroless and scratch: minimal runtime
Distroless images contain only your app and its runtime libraries: no shell, no package manager. Benefits:
- fewer CVEs in scanners, and less for an attacker to use if they get in;
- smaller images.
Trade-offs:
- no shell for
docker execdebugging. Usedocker debug, an ephemeral debug container, or the:debugdistroless variants (which include busybox) in staging; - you must build in another stage. See multi-stage builds.
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/app
FROM gcr.io/distroless/static-debian12
COPY --from=build /app /app
USER nonroot
ENTRYPOINT ["/app"]
Size isn’t everything
- Layers are cached and shared: if several images use the same base, it’s stored and pulled once.
- A 30 MB difference rarely matters on a server. Build reliability and debuggability usually matter more.
- The biggest savings come from multi-stage builds (no compilers or build tools in the final image), and cleaning package caches. See Docker image size reduction.
Security
Fewer packages means fewer vulnerabilities to patch. Whatever you choose:
- pin and regularly rebuild the base image. See Docker image tags best practices;
- scan images. See Trivy image scanning;
- run as a non-root user. See Docker security best practices.
Recommendations
| App type | Suggested final base |
|---|---|
| Go / Rust static binary | scratch or distroless/static |
| Python | python:3.x-slim (Debian); distroless/python for hardened builds |
| Node.js | node:22-slim; Alpine only if native modules work |
| Java | eclipse-temurin:21-jre (Ubuntu-based) or distroless/java |
| PHP | php:8.3-fpm (Debian); Alpine variants are popular too |
| Utilities and proxies | Alpine |
Quick reference
| Priority | Choose |
|---|---|
| Compatibility | Debian slim |
| Smallest general-purpose | Alpine |
| Minimal attack surface | Distroless / scratch |
| Easy debugging | Debian slim / Ubuntu |
Related: Dockerfile tutorial and build multi-arch images.