Base Image
The starting point of a Dockerfile specified in the FROM instruction. The base image determines the operating system, package manager, and initial filesystem of the final image — and is the single biggest factor in image size and security.
Base Image — The Foundation of Every Docker Image
What Is a Base Image in Simple Terms?
Every Docker image starts from something. That something is the base image — specified in the FROM instruction at the top of your Dockerfile. The base image provides the operating system environment, the package manager, and the language runtime (if any) that your application runs on top of.
Choosing the right base image is the single most impactful decision in Dockerfile writing. The wrong base image adds 1GB to your image size and 200 CVEs to your security exposure before you write a single line of your own code.
+------------------------------------------+| FROM node:20-alpine <- base image || || Provides: || Alpine Linux 3.18 (7MB OS) || Node.js 20 runtime (48MB) || npm package manager || Total: ~55MB |+------------------------------------------+| Your application layers go on top || COPY, RUN, CMD instructions add layers |+------------------------------------------+Base Image Options for Node.js — Size and Security Comparison
# Pull and comparedocker pull node:20 # 1.1GB — full Debian + build toolsdocker pull node:20-slim # 220MB — Debian without build toolsdocker pull node:20-alpine # 55MB — Alpine Linux (musl libc)docker pull gcr.io/distroless/nodejs20-debian12 # 120MB — no shell at all # CVE comparisontrivy image node:20 # ~200 CVEs (CRITICAL + HIGH)trivy image node:20-alpine # ~0 CVEs # node:20-alpine wins on size and security for most appsWhen to Use Each Base Image
node:20-alpine Use when: most Node.js applications Pros: smallest size, fewest CVEs Cons: uses musl libc — some native modules break Test: bcrypt, canvas, sharp sometimes fail on Alpine node:20-slim Use when: app uses native modules that need glibc Pros: glibc compatible, smaller than full node:20 Cons: larger than alpine, more CVEs than alpine Test: most native modules work here node:20 (full Debian) Use when: build stage only — needs gcc, python, make Never use as final production stage Size: 1.1GB — too large for production distroless/nodejs20 Use when: maximum security, no debugging needed Pros: no shell, no package manager = minimal attack surface Cons: cannot exec in for debugging Use with: multi-stage builds only scratch (empty image) Use when: statically compiled binaries (Go, Rust) Pros: absolute minimum — just your binary Cons: no shell, no certs, no timezone data Requires: static linking (CGO_ENABLED=0 for Go)Pinning Base Images for Reproducibility
# BAD — tag can change, build is not reproducibleFROM node:20-alpine # BETTER — specific version pinnedFROM node:20.10.0-alpine3.18 # BEST — pinned by digest (immutable, always exact same bytes)FROM node:20-alpine@sha256:a84f9c2b1d3e...# Get the digest:# docker pull node:20-alpine# docker inspect node:20-alpine --format '{{index .RepoDigests 0}}'Keeping Base Images Updated
# Check your current base image for CVEstrivy image node:20-alpine # Use Renovate or Dependabot to automate base image updates# These tools open PRs when new base image versions are available# Your CI scans the new image before merging# Combine: automated updates + CI scanning = always current and secureTipAlways check if a
-alpinevariant exists for your language runtime before accepting the default.node:20vsnode:20-alpineis a 1GB vs 55MB difference — a 20x size reduction with no application code changes required.
Common MistakeUsing the full
node:20orpython:3.11base image in production. These are designed for development convenience — they include build tools, compilers, and debugging utilities that serve no purpose in a production container and add hundreds of CVEs.
Frequently Asked Questions
Why does base image choice matter more than almost any other Dockerfile decision?
The base image sets the OS layer, package manager, libc implementation, and every pre-installed binary the final image inherits, and it's typically the single largest layer by size — a `python:3.12` base is roughly 900MB+ versus `python:3.12-slim` at under 200MB or `python:3.12-alpine` under 60MB. It also defines your CVE surface: a full Debian base ships hundreds of packages you never use, each a potential vulnerability that security scanners will flag even though your app never touches them.
What's a common mistake when switching to a smaller base image like Alpine?
Assuming Alpine is a drop-in replacement for a Debian/Ubuntu-based image. Alpine uses musl libc instead of glibc, which breaks some Python/Node native binary wheels compiled against glibc, causing cryptic segfaults or 'symbol not found' errors at runtime rather than build time. For apps with compiled dependencies, `-slim` variants (Debian-based, glibc-compatible) are often a safer size reduction than Alpine, which is better suited to statically-linked binaries with no native extensions.