Safeguard
Container Security

How to Choose a Secure Base Image

Base image choice sets your CVE floor before any scanner runs. Here's how to evaluate footprint, patch cadence, provenance, and rebuild cycle.

Priya Mehta
DevSecOps Engineer
8 min read

Every container image starts with a base layer you didn't write, and that layer often accounts for more vulnerabilities than the application code stacked on top of it. A 2023 Anchore analysis of Docker Hub's top 10 most-pulled images found that popular official bases — node, python, and golang among them — shipped between 200 and 500+ known CVEs in their default latest tags before a single line of app code was added. Swapping a bloated general-purpose base for a minimal or distroless equivalent can cut that number by 90% or more with zero application changes. Base image selection is one of the highest-leverage decisions in a container security program: you make it once per build, it propagates into every downstream image and every running pod, and it sets your CVE floor before any scanner even runs. This guide covers the concrete criteria — package footprint, patch cadence, provenance, and rebuild cycle — that separate a secure base from a liability, and how to evaluate them without guessing.

What actually makes a base image "secure"?

A secure base image is defined by what it excludes, not what it includes — fewer packages means fewer CVEs, full stop. The ubuntu:22.04 image ships with roughly 90+ installed packages including a shell, a package manager, and utilities like curl and openssl that an application almost never needs at runtime. Each of those packages is a potential CVE source: a 2022 Chainguard study comparing ubuntu:22.04 against a Wolfi-based equivalent found the Ubuntu image carried 40+ known vulnerabilities at time of build versus fewer than 5 for the minimal alternative, with no functional difference for a typical Go or Java workload. Google's distroless images take this further by removing the shell and package manager entirely, which also closes off a common post-exploitation technique — an attacker who achieves remote code execution has no /bin/sh to pivot into. When you're picking a base, start by asking what the running container actually needs at execution time (a runtime, maybe a few shared libraries) versus what it needed at build time (compilers, package managers, dev headers) — those are almost always different images, and conflating them is the single most common base image mistake.

Distroless, Alpine, or minimal Debian — which one should you actually use?

There's no universal winner; the right choice depends on your language runtime and how much you're willing to trade convenience for attack surface. Alpine (built on musl libc, ~5-7MB base) is popular for Go and static binaries because Go doesn't depend on glibc, but it has caused real production incidents for Python and Node workloads — musl's different DNS resolution and threading behavior broke getaddrinfo-based lookups in several documented cases, which is why some teams avoid Alpine for anything using native extensions (e.g., Python's numpy/pandas wheels compiled against glibc). Distroless images (gcr.io/distroless/*, roughly 20MB for the base variants) are the tightest option for compiled languages like Go, Java, and Rust, since they include only the language runtime and its direct dependencies with no shell at all. Chainguard's Wolfi-based images sit in between: glibc-compatible (so no musl surprises), rebuilt daily, and typically 0-CVE at publish time according to Chainguard's own scan dashboards. For teams standardizing on Debian for compatibility reasons, debian:12-slim (~74MB vs. debian:12's ~124MB) removes man pages, docs, and many optional packages while keeping glibc and apt, making it a reasonable middle ground when distroless breaks something you depend on.

How do you actually check a base image's vulnerability history before adopting it?

Check the image's CVE count at the specific tag and digest you intend to pin, not the friendly latest tag, because vulnerability counts vary wildly between tags of the same repository. Running docker scout cves or grype against python:3.11 versus python:3.11-slim versus python:3.11-alpine on the same day routinely shows a 5-10x difference in reported CVEs — the full python:3.11 image includes build tools and dev libraries the slim variant drops. Beyond a point-in-time count, look at the maintainer's patch cadence over the last 90 days: official Docker images and Chainguard images are typically rebuilt within 24-48 hours of an upstream CVE disclosure, while many community-maintained images on Docker Hub go months without a rebuild — one 2023 Sysdig survey of the top 50 Docker Hub images found roughly a third hadn't been updated in over 6 months. A base image with zero CVEs today but no rebuild history is a worse long-term bet than one with a handful of low-severity CVEs and a same-week patch record, because the second one will still be current when the next libssl or zlib CVE lands.

Does image provenance and signing matter as much as CVE count?

Yes — an image with a clean CVE scan but no verifiable origin is a supply chain risk regardless of what the scanner says, because you have no guarantee the image you're pulling is the image the maintainer actually built. The 2021 Codecov breach and the broader wave of typosquatted Docker Hub images (security researchers have repeatedly found cryptomining payloads hidden in images mimicking popular names like nginx or redis with subtly altered spellings) both stemmed from teams pulling unverified images by name alone. Look for images that publish signed attestations via Sigstore/cosign and provide a Software Bill of Materials (SBOM) alongside the image — Chainguard, Google's distroless project, and increasingly Docker's own "Docker Official Images" program all support this. Pin by SHA256 digest, not tag, in production Dockerfiles (FROM python@sha256:abc123... rather than FROM python:3.11-slim), since tags are mutable pointers that can be repointed to a different — potentially compromised — image without your build pipeline noticing.

How often should you actually rebuild and repin your base image?

Rebuild on every base image update from the upstream maintainer, and audit your pinned digests at least monthly even if nothing in your own code has changed. A base image pinned six months ago is a static snapshot accumulating newly disclosed CVEs against packages that haven't moved — the image itself doesn't get safer by sitting still. Chainguard's daily-rebuild model exists specifically to address this: their images are rebuilt roughly every 24 hours against the latest upstream patches, so a digest pinned last week already reflects a week of accumulated fixes rather than a week of accumulated risk. Practically, this means your CI pipeline needs an automated dependency-update mechanism (Renovate or Dependabot configured for Docker FROM lines) that opens a PR whenever the upstream base image publishes a new digest, and a policy — many teams use 30 days — for how long a stale base image PR can sit unmerged before it blocks a release.

How much does image size actually matter for security, versus just performance?

Smaller images matter for security specifically because size correlates with installed package count, and installed packages are the attack surface. It's not size itself — a 500MB image of static assets isn't riskier than a 50MB one — it's that a smaller base image in practice means fewer binaries an attacker can use to escalate or move laterally after an initial compromise. A distroless image with no shell means a successful RCE against your application still can't spawn /bin/bash, read /etc/passwd interactively, or use curl to exfiltrate data to an external host, because none of those binaries exist in the container. This is why security teams increasingly track "binary count" or "shell presence" as a base image metric alongside raw CVE totals — a 2024 Chainguard-commissioned analysis found that over 65% of container escape and lateral-movement techniques documented in MITRE ATT&CK's container matrix depend on a shell or common Unix utility being present in the compromised container.

How Safeguard Helps

Picking a clean base image is necessary but not sufficient — you still need to know which of the CVEs in that image, and in every dependency layered on top of it, are actually reachable from your application's code paths. Safeguard's reachability analysis traces call graphs through your SBOM to tell you which base-image and dependency vulnerabilities are exploitable in your specific build versus theoretical noise, cutting remediation queues down to what matters. Griffin AI, Safeguard's autonomous triage engine, correlates that reachability data against exploit maturity and your deployment context to prioritize the handful of findings that need action this week. Safeguard generates and ingests SBOMs directly from your image builds — including third-party and Chainguard-style minimal bases — so provenance and package inventory are tracked automatically rather than reconstructed manually during an audit. When a safer base image or patched digest is available, Safeguard opens an auto-fix PR with the updated FROM line and a diff of what changed in the underlying package set, so the fix ships as fast as the finding was identified.

Never miss an update

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