20.1.2.2 First Base Image Choice
A focused guide to First Base Image Choice, connecting core concepts with practical Docker and container operations.
The base image is the first layer of every Docker image. Every FROM instruction names a starting point whose filesystem, installed packages, and configuration become the foundation of your image. Choosing the right base image affects image size, security surface, build speed, compatibility, and operational simplicity.
What a Base Image Provides
A base image supplies:
- A filesystem root with an operating system layout (directories like
/bin,/lib,/etc,/usr). - A package manager (apt, apk, yum, or none at all for scratch images).
- System libraries and runtime tools.
- Potentially a language runtime (Python, Node.js, Java, Go, etc.) if you use an official language image.
When you write FROM ubuntu:22.04, your subsequent RUN, COPY, and CMD instructions operate on top of that Ubuntu 22.04 filesystem. When you write FROM python:3.12-slim, the image already contains Python 3.12 and its standard library, and you build your application on top of it.
The Major Categories of Base Images
Scratch — The empty image. Contains nothing at all. Used for statically compiled binaries (Go, Rust) that have no runtime dependencies:
FROM scratch
COPY my-binary /my-binary
CMD ["/my-binary"]
The resulting image contains only the binary. Size is typically 5–20MB. No shell, no package manager, no OS utilities — the smallest possible attack surface.
Alpine Linux — A minimal Linux distribution using musl libc and the apk package manager. Base image size is approximately 7MB. Used when you need a shell and package manager but want to keep the image small:
FROM alpine:3.19
RUN apk add --no-cache curl
Alpine is widely used, but applications built against glibc may behave differently with musl. Some compiled extensions (Python C extensions, certain Node.js packages) require glibc and will fail on Alpine without workarounds.
Debian/Ubuntu Slim — Reduced-footprint variants of Debian or Ubuntu that remove documentation, locales, and optional tools. Provide glibc compatibility without the full OS overhead. debian:bookworm-slim is approximately 75MB; ubuntu:22.04 is approximately 78MB.
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
Official Language Images — Pre-built images maintained by language maintainers on Docker Hub. They exist in multiple variants per version:
| Image | Variant | Approximate Size |
|---|---|---|
node:20 | Full Debian | ~1.1GB |
node:20-slim | Debian Slim | ~230MB |
node:20-alpine | Alpine | ~135MB |
python:3.12 | Full Debian | ~1.0GB |
python:3.12-slim | Debian Slim | ~130MB |
python:3.12-alpine | Alpine | ~50MB |
openjdk:21-jdk | Full Debian | ~430MB |
eclipse-temurin:21-jre-alpine | Alpine JRE only | ~180MB |
Distroless — Images maintained by Google that contain only the application runtime and its dependencies, with no shell, no package manager, and no OS utilities. Used for production deployments where the attack surface must be minimal but static binaries are not feasible:
FROM gcr.io/distroless/java21-debian12
COPY app.jar /app.jar
CMD ["/app.jar"]
Comparing Base Images
Image Variants and Tags
Official images on Docker Hub use tag conventions to differentiate variants. Understanding these tags avoids accidentally pulling a 1GB full image when a 130MB slim variant would do:
- No suffix (e.g.,
python:3.12): Full image, usually Debian-based with all tools. -slim: Debian-based, stripped of non-essential packages.-alpine: Alpine-based, minimal size, musl libc.-bullseye/-bookworm: Specific Debian release codenames.-jrevs-jdk: Java Runtime Environment (no compiler) vs Java Development Kit (includes compiler). Use-jrefor runtime containers.
To see available tags for an image on Docker Hub:
docker search python
Or browse hub.docker.com/_/python for the full tag list.
Pulling and Inspecting a Base Image
To pull a base image without building anything:
docker pull node:20-alpine
To check the size after pulling:
docker images node
REPOSITORY TAG IMAGE ID CREATED SIZE
node 20-alpine a1b2c3d4e5f6 2 weeks ago 135MB
node 20-slim b2c3d4e5f6a1 2 weeks ago 231MB
node 20 c3d4e5f6a1b2 2 weeks ago 1.1GB
To inspect what is inside the base image:
docker run --rm -it node:20-alpine sh
Alpine uses sh, not bash. Inside, you can explore the filesystem, check what is installed with apk list --installed, or verify the Node.js version with node --version.
Choosing the Right Base Image for a First Project
For a first Dockerfile, the following choices are practical:
- Node.js applications:
node:20-alpineoffers a good balance of size and compatibility. Switch tonode:20-slimif Alpine causes issues with native module compilation. - Python applications:
python:3.12-slimis reliable and avoids Alpine musl compatibility issues common with Python C extensions. - Java applications:
eclipse-temurin:21-jre-alpinefor runtime-only containers. Avoidopenjdk:21-jdkin production images. - Go or Rust applications: Build in a full SDK image using a multi-stage build, then copy the compiled binary into
scratchoralpinefor the final image. - Arbitrary tools and scripts:
alpine:3.19if you just need a shell and package manager.
Pinning the Base Image Version
Using latest as the tag is convenient but unpredictable. The latest tag points to whatever the maintainer considers current, and it changes when new versions are released:
FROM node:latest # unpredictable — changes whenever Node.js releases
FROM node:20-alpine # stable — the Node.js 20 line on Alpine
FROM node:20.11.1-alpine3.19 # fully pinned — exact version
For production images, pin to at least the minor version (node:20-alpine) or the full version (node:20.11.1-alpine3.19) to ensure reproducible builds.
Security and Update Considerations
Base images inherit vulnerabilities from their included packages. A full Ubuntu image ships hundreds of packages, each of which may carry CVEs. Minimal images (Alpine, Slim, Distroless) reduce the installed package count and thus the potential vulnerability surface.
To check for vulnerabilities in a base image:
docker scout cves node:20-alpine
Docker Scout (included with Docker Desktop) scans the image and reports known CVEs in its packages. Updating the base image tag periodically (or automating image rebuilds) keeps the base layer up to date with security patches.