✦ For everyone, free.

Practical knowledge for real and everyday life

Home

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:

ImageVariantApproximate Size
node:20Full Debian~1.1GB
node:20-slimDebian Slim~230MB
node:20-alpineAlpine~135MB
python:3.12Full Debian~1.0GB
python:3.12-slimDebian Slim~130MB
python:3.12-alpineAlpine~50MB
openjdk:21-jdkFull Debian~430MB
eclipse-temurin:21-jre-alpineAlpine 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 Size Shell Pkg Mgr Use scratch 0 MB No No Static bins alpine:3.19 ~7 MB Yes apk Minimal apps debian:bookworm-slim ~75 MB Yes apt glibc apps node:20-slim ~230 MB Yes apt Node.js apps distroless/nodejs20 ~130 MB No No Production

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.
  • -jre vs -jdk: Java Runtime Environment (no compiler) vs Java Development Kit (includes compiler). Use -jre for 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-alpine offers a good balance of size and compatibility. Switch to node:20-slim if Alpine causes issues with native module compilation.
  • Python applications: python:3.12-slim is reliable and avoids Alpine musl compatibility issues common with Python C extensions.
  • Java applications: eclipse-temurin:21-jre-alpine for runtime-only containers. Avoid openjdk:21-jdk in production images.
  • Go or Rust applications: Build in a full SDK image using a multi-stage build, then copy the compiled binary into scratch or alpine for the final image.
  • Arbitrary tools and scripts: alpine:3.19 if 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.