Safeguard
Container Security

Minimal Base Images for Security: A Practical Guide

Minimal base images cut CVE counts by up to 95% by shipping only what your app needs. Here is how to choose between distroless, Wolfi, Alpine, and scratch — and build on each safely.

Marcus Chen
Cloud Security Engineer
5 min read

The single highest-leverage decision for container security is the base image, because most of the CVEs a scanner reports live in OS packages you never chose to use — they came bundled with a full distribution. A stock ubuntu:22.04 carries 80 to 100 packages; a python:3.12 image weighs close to a gigabyte uncompressed. A minimal base image ships only your app, its runtime, and the handful of libraries it genuinely needs, which routinely removes 60 to 95% of OS-level findings simply because the vulnerable packages are no longer present. Chainguard scan comparisons have shown a python:3.12-slim image carrying well over a hundred known CVEs while an equivalent hardened minimal image reports zero at build time. This guide covers the main minimal-base options and how to build on each without giving up more than you have to.

The main minimal-base options

There is no single "most secure" base — the right one depends on your language and runtime.

  • scratch — literally empty. For fully static Go or Rust binaries. Zero OS attack surface.
  • Distroless (gcr.io/distroless/*) — your runtime plus minimal libraries, no shell, no package manager. Language variants for Java, Python, Node.js.
  • Wolfi / Chainguard (cgr.dev/chainguard/*) — a distro built specifically to produce minimal images with zero known CVEs at build time, updated continuously.
  • Alpine (alpine:3.21) — ~7 MB, keeps a shell and apk, so it is minimal-ish but not shell-free.
  • -slim variants — a full distro with docs and extras removed. The easiest migration, the least reduction.

Building on a minimal base

Every minimal base assumes a multi-stage build: compile or install in a full-featured stage, then copy only the artifact into the minimal final stage.

# Go service on scratch
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/server

FROM scratch
COPY --from=build /app/server /server
# scratch has no CA bundle — copy one if you make TLS calls
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/server"]

For interpreted languages, use the matching distroless runtime and keep the non-root variant.

FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/deps -r requirements.txt

FROM gcr.io/distroless/python3-debian12:nonroot
WORKDIR /app
COPY --from=build /deps /deps
COPY . .
ENV PYTHONPATH=/deps
USER 65532:65532
ENTRYPOINT ["main.py"]

Always pin the base by digest so a mutable tag cannot swap the image underneath you.

# Resolve a tag to its immutable digest before pinning
docker buildx imagetools inspect gcr.io/distroless/static-debian12:nonroot

Choosing a minimal base

BaseBest forShell?Notable tradeoff
scratchstatic Go / Rustnoyou supply CA certs, tzdata yourself
distrolessmost compiled + interpreted appsnodebug via ephemeral containers
Wolfi / Chainguardteams wanting zero-CVE baselinesoptionalsome images behind a subscription
Alpinesmall images needing a shellyesmusl compatibility, apk present
-slimfastest migrationyessmallest security gain

A practical migration order keeps the operational risk low: start with stateless, statically linked services that map cleanly onto scratch or distroless/static, then move interpreted-language services onto their matching distroless runtime, validating startup, health checks, and logging at each step. Update runbooks to use ephemeral debug containers before you cut over on-call teams, and pin every base by digest so a rollback is deterministic. Rebuild on a weekly schedule even when your code has not changed, so you inherit upstream patches rather than freezing a base at the version you first pulled.

How Safeguard scans your images

Minimal bases shrink the OS-package problem but not the vulnerabilities in your application's own dependencies, which live in the app layer no matter how small the base is. Safeguard scans both, reading components directly from filesystem layers and language manifests so it inventories even a scratch image with no package manager to query. It runs reachability analysis so the findings you triage are the ones exploitable from code your service actually calls, emits a CycloneDX or SPDX SBOM per build, and opens auto-fix pull requests when a base-image bump or dependency patch resolves a reachable finding. Run it in CI with the Safeguard CLI, gate pull requests with Secure Containers, and cover the dependency tree with software composition analysis. You can start on a single repository from pricing that begins at a dollar.

Frequently Asked Questions

Which minimal base should I default to?

Distroless is the safe default for most services: no shell, no package manager, and language-specific variants that need minimal changes. Use scratch only for fully static binaries, and consider Wolfi/Chainguard if you want a continuously rebuilt zero-CVE baseline.

Do minimal images break TLS or timezones?

They can. scratch and some distroless images do not include a CA certificate bundle or timezone data, so HTTPS calls or local-time formatting fail until you copy ca-certificates.crt and tzdata in from the build stage. Distroless base variants include the CA bundle.

Is a minimal base a replacement for scanning?

No. It reduces how many OS CVEs you start with, but your application dependencies — the npm, PyPI, or Maven packages you pull in — ship regardless of base. Log4Shell rode into production inside application layers on every base image. Minimize and scan.

How do I debug a minimal image in production?

Use an ephemeral debug container that brings its own tools, such as kubectl debug -it pod --image=busybox --target=app, rather than adding a shell to the runtime image. Many distroless images also publish a :debug tag for local troubleshooting.

Want to see how much a minimal base cuts your CVE count? Connect a repository at app.safeguard.sh/register, and read the base-image scanning guide in the documentation at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.