Migrate from public base images to hardened container images: a step-by-step guide (2026)

By
Yael Nardi
May 16, 2026

A hardened container image is a minimal base layer built from auditable upstream source, signed at publication, accompanied by a Software Bill of Materials (SBOM) and a Vulnerability Exploitability eXchange (VEX) document, and patched against critical CVEs on a published, contractual cadence. The base image is the one piece of a containerized application most teams inherit without auditing.

It is also the layer that decides whether every higher control (admission policies, runtime monitors, network policies) has a small or a large attack surface to defend.

Across the base-image migrations I have led for regulated cloud teams over the past two years, the same pattern keeps surfacing. The hard part is never the FROM-line swap. It is producing the audit table that justifies the swap, and the admission policy that prevents the next CI run from undoing it.

This guide walks the four-phase migration that takes a fleet of public base images to hardened equivalents without breaking production: audit, map, stage, validate.

Key Takeaways

  • Migrating from public to hardened container images is a four-phase project (audit, map, stage the rollout, validate) that typically delivers a 90–98% drop in CVE count and an up-to-95% drop in attack surface. Docker DHI's internal Node migration: vulnerabilities to 0, packages reduced 98%. The python:3.13 to dhi.io/python:3.13 swap per the Docker Scout quickstart: 1 High + 5 Medium + 141 Low to 0/0/0, 412 MB to 35 MB, 610 packages to 80.
  • The audit phase is where most migrations fail, not the swap itself. A popular Debian-based public image carries roughly 300 CVEs and ~300 components on average. Only ~5% of those CVEs disappear from a simple package-update pass, per Chainguard's 2024 State of Hardened Container Images report. "Just patch it" is not a migration strategy.
  • There is no single "right" hardened image. The eight production-grade families currently shipping in 2026 are Docker Hardened Images, Chainguard Containers, Wolfi-direct, Red Hat UBI Micro, Canonical Chiselled Ubuntu, BellSoft Alpaquita, Iron Bank, and source-built minimal alternatives. They trade off across libc, signing, SBOM and VEX availability, patch SLA, and compliance posture (FIPS 140-3, STIG, FedRAMP, NIST SP 800-190, CIS).
  • The migration only sticks if it is enforced at admission. Without a Kyverno, OPA Gatekeeper, or Ratify policy that rejects unsigned, un-SBOM'd, or stale images, the next CI run silently regresses the cluster back to a public image inside six months.

1. Auditing Your Current Base Images: Understanding Your CVE Exposure

Auditing your current base images means producing a complete, scored inventory of every base image your organization runs in production. The inventory covers CVE counts by severity, days since the last upstream rebuild, signature and SBOM availability, and compliance posture.

The output is a migration backlog prioritized by real risk, not by which Dockerfile you happened to open first. This is the first step that Chainguard's 2024 report and NIST SP 800-190 section 4.1.1 (Inventory) both require, and it is the chapter every existing migration tutorial skips.

Every audit I have run for a Fortune 500 platform team has started here. Every single one has produced the same shock: nobody had a single source of truth for which FROM lines were in production, and at least one critical compliance workload was inheriting a base image that nobody on staff could name.

Step 1: Inventory Every Base Image Across Your Registries and Clusters

Run a registry-wide enumeration, not a single docker scout quickview:

  • Across registries: crane catalog and crane manifest (or gcrane) walk every image:tag pair.
  • Across running clusters: kubectl get pods --all-namespaces -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\n' | sort -u.
  • Per image: docker history --no-trunc <image>, docker inspect <image>, or dive to walk layers and identify the bottommost distribution image.
  • For images that ship an SBOM: parse it directly with syft <image> -o cyclonedx-json or grab the attached attestation with cosign download attestation <image>@sha256:<digest>.

The output is a single table. One row per unique base image. Columns for "used by N production workloads," "first introduced," "last upstream rebuild," and "registry source." This is the input for package comparison-style scoring in step 2.

Step 2: Score Each Base Image on the Six-Criteria Audit Rubric

Audit Criterion What to Capture Pass Threshold
CVE count by severity Total Critical / High / Medium / Low (multi-engine: Trivy + Grype, or Trivy + Snyk) 0 Critical, 0 unfixed High
KEV exposure Number of CVEs in CISA's Known Exploited Vulnerabilities catalog 0
Maintenance staleness Days since the last upstream rebuild (docker manifest inspect → created) < 30 days
Provenance + signing Cosign or Notation signature; SLSA Build Level (target L3+) Signed + L3
SBOM + VEX availability CycloneDX or SPDX SBOM attached? VEX document present? Both attached
Compliance posture Mapped to NIST SP 800-190, CIS Docker Benchmark, FIPS 140-3, STIG, FedRAMP? Maps to the frameworks your org owes

The numbers your audit produces will not be small. Chainguard's 2024 State of Hardened Container Images report puts an average Debian-based community image at ~300 CVEs and ~300 components, an average Red Hat-provided image at ~200 CVEs (excluding "will not fix"), and the top 50 Iron Bank images at ~110 CVEs.

Snyk's recurring scanning data shows 44% of Docker image scans contain known vulnerabilities for which a more secure base image is already available.

Score by CVE blast radius, which is severity multiplied by the number of workloads inheriting the image. Do not score by raw CVE count. A High CVE in a glibc, openssl, or libxml2 package on a base image used by 40 services is a higher-priority migration target than a Critical on a single internal tool.

The first time I watched a team prioritize a Critical-only-by-CVSS finding ahead of a High that affected 60 inherited workloads, the patch landed and the actual blast-radius CVE sat for another sprint. That anti-pattern is what the rubric is built to prevent.

Step 3: Produce a Prioritized Migration Backlog

Base Image CVE-blast-radius score KEV count Days stale Signed? SBOM? Compliance gap Migration priority
node:20 380 2 47 No No NIST 4.1.1 Wave 1 (week 1–2)
python:3.13 220 0 12 No No NIST 4.1.1 Wave 2 (week 3–4)
nginx:1.27-alpine 90 0 8 No No NIST 4.1.1 Wave 3 (week 5–6)
gcr.io/distroless/static:nonroot 0 0 5 Yes Yes Already passes Skip / keep

This backlog table is the deliverable that justifies the migration to your security and platform-engineering audiences. It is also the minimal attack surface baseline you measure against in phase 4.

2. Mapping Public Images to Hardened Equivalents: Finding Your Drop-In Replacements

Mapping each public base image to a hardened equivalent is a vendor-neutral, workload-aware decision. The same image (node:20-alpine, say) maps differently depending on whether you need a glibc runtime, FIPS-validated cryptography, a published 7-day patch SLA, or DoD-grade STIG conformance.

Eight production-grade hardened-image families currently ship in 2026, split along three axes: minimization technique (minimal distro, distroless, build-from-source minimal), libc (glibc vs. musl), and commercial posture (free or open vs. paid with SLA).

The Vendor-Neutral Mapping Table

If you currently run Drop-in candidates (production-grade, 2026) Reason
ubuntu:22.04 / ubuntu:24.04 Canonical Chiselled Ubuntu 24.04, dhi.io/ubuntu, source-built minimal Ubuntu Same userspace + LTS security backports + minimization
debian:12-slim dhi.io/debian, Wolfi (Chainguard public), source-built minimal Debian Drop-in for glibc workloads with debian-style package layout
alpine:3.20 dhi.io/alpine, Wolfi (Chainguard public), source-built minimal Alpine musl-compatible; Wolfi switches you to glibc + apk if you want
node:20, node:20-alpine dhi.io/node, cgr.dev/chainguard/node, source-built minimal Node Most common migration target; covered in every vendor's quickstart
python:3.13, python:3.13-slim dhi.io/python, cgr.dev/chainguard/python, gcr.io/distroless/python3-debian12, source-built minimal Python Watch for pip install behavior in shellless variants
openjdk:21, eclipse-temurin:21 BellSoft Alpaquita Liberica JDK, dhi.io/temurin, Chainguard Java, source-built minimal Java LTS Java backports + JVM-tuned minimization
golang:1.24 (build) → gcr.io/distroless/static-debian12 (run) dhi.io/golang → dhi.io/static; Chainguard Go → Chainguard static; source-built minimal Go → source-built minimal static Multi-stage is the canonical Go pattern; keep it
nginx:1.27-alpine dhi.io/nginx, Chainguard nginx, source-built minimal nginx Highest-leverage swap; every gateway pod inherits
redis:7, postgres:16, mongo:7 Vendor-hardened variant if available; otherwise DHI / Chainguard / source-built equivalents Watch for default UID/GID and data-volume permissions
Windows Server Core / Nano Server Microsoft Windows Server Core / Nano Server Only Microsoft can publish Windows base images per Microsoft Learn
DoD / FedRAMP-regulated workload Iron Bank (repo1.dso.mil/dsop), Chainguard FIPS variants (400+ CMVP-validated images), DHI Enterprise FIPS/STIG DoD/FedRAMP accreditation already in place

Pick rows that satisfy your compliance posture first, then optimize for patch SLA, then for image size. Never the reverse.

I have personally watched a "size-first" migration unwind itself when a Critical CVE landed on a 4 MB image with a 30-day community patch latency, while the 40 MB hardened equivalent rebuilt and shipped within 48 hours.

Docker's December 2025 free-DHI launch under Apache 2.0 made the category economically accessible to every team. The Coalfire/Chainguard FedRAMP session documents how a hardened-image vendor's 7-day patch SLA outperforms the FedRAMP 30/90/180-day window for Critical, High, and Moderate findings.

What Breaks During Migration: The 2 AM Playbook

Risk Class Symptom Root Cause Mitigation
musl ↔ glibc incompatibility Binary segfaults; Error loading shared library Compiled against one libc, run on the other Pick a candidate with the matching libc; do not mix Alpine apks into a Wolfi image
Missing shell at debug time kubectl exec returns OCI runtime exec failed: exec: "sh" Distroless runtime by design Use kubectl debug ephemeral container, cdebug, chainctl images diff, or a temporary -dev rebuild
BusyBox vs. coreutils behavior Shell scripts fail on flag differences Dev variants ship BusyBox; standard utils are stricter Test scripts against the dev-variant build; replace groupadd/useradd with addgroup/adduser
Entrypoint difference from upstream Container starts, app never runs Hardened image set a non-root user or different entrypoint Read the image's spec page; explicitly set USER and ENTRYPOINT in your Dockerfile
Default non-root user App fails to bind port < 1024 or write to /var/log Most hardened images run as a non-root UID Bind to ports ≥ 1024; chown writeable volumes to the hardened-image UID
CA-certificate path differs TLS handshake failures to upstream APIs Distroless variant placed CA bundle at a non-Debian path Use update-ca-certificates in a -dev builder stage and COPY --from=builder /etc/ssl/certs/ca-certificates.crt to the runtime stage
Init / PID 1 reaping Zombie processes accumulate Hardened image is a single binary, not a full init system Use tini as the entrypoint, or pick a variant that ships a minimal init
Custom packages missing command not found for curl, bash, git Hardened runtime intentionally omits these Move into a multi-stage builder (with -dev or upstream image), or add via custom assembly / a private APK repo

Every one of these risks has bitten me in production at least once. The CA-certificate path issue is the one I flag for every team I onboard, because it produces TLS errors that look like upstream API outages. The on-call rotation almost always misdiagnoses it on the first incident.

Hardened Container Images: The Foundation of Container Security is the deeper-dive companion on why these eight rows exist as a category. Minimal Distroless Images: Benefits Beyond Security is the comparison-shopping companion for the distroless rows.

3. Making the Switch: A Safe, Staged Rollout Without Breaking Production

A safe rollout is a six-stage pipeline discipline. Pick the lowest-risk pilot workload, multi-stage your build to keep upstream tooling out of production, swap one FROM line at a time, pin the new image by digest, run it through dev → staging → canary → blue-green → 100% gates, and ship a written rollback plan with every wave.

The single highest-leverage lesson, lifted from the Chainguard Academy migration overview and re-confirmed in every multi-service migration I have led: migrate application images before base images. Less coordination overhead, fewer downstream dependencies to flush. The eng leads on individual services see the win before the platform team has to defend the migration to leadership.

Pick the Lowest-Risk Pilot Workload First

A workload is a good pilot when it is stateless, runs a single language runtime with no native extensions, has mature canary or blue-green automation, and has a named on-call rotation that owns it.

A workload is a bad pilot when it is stateful (Postgres or Redis with persistent volumes), runs cron or batch jobs (test surface is sparse), or carries a customer-facing SLA in the next 30 days.

A gateway or API service is the canonical first pick: high traffic, fast feedback loop, easy to canary, and the security win on a hardened nginx or Envoy pod is the most defensible to a CISO. On every migration I have led, picking the right pilot has been the single biggest predictor of how the rest of the rollout goes. Pick a stateful Wave-4-grade workload first and the political capital you need for Waves 2 through 4 is gone.

Use a Multi-Stage Build to Keep Upstream Tooling Out of Production

# Builder stage uses the upstream image (or vendor's -dev variant); ephemeral
FROM golang:1.24 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/server ./cmd/server

# Runtime stage uses the hardened distroless image
FROM <hardened-static-image>:<digest>
COPY --from=builder /out/server /server
USER 65532:65532
ENTRYPOINT ["/server"]

The build-time and runtime images should almost never be the same. Compilers, package managers, and shell utilities belong in the builder stage and never reach production.

Pin by Digest, Never by Tag

FROM nginx:1.27 may pull different bytes today versus last month if the maintainer rebuilt the tag. The same is true of hardened-image tags. Pin to the immutable content digest:

# base image: dhi.io/nginx:1.27-alpine, refreshed weekly

FROM dhi.io/nginx@sha256:<full digest>

Renovate or Dependabot bumps the digest automatically on a weekly cadence (config in §4.3 below). Every team I have onboarded to a digest-pinning policy has initially complained about merge friction, then stopped complaining the first time Renovate caught a silent base-layer rebuild before it reached staging.

Verify Signature, SBOM, and Provenance Before the Build Runs

cosign verify \
  --certificate-identity "$EXPECTED_IDENTITY" \
  --certificate-oidc-issuer "$EXPECTED_ISSUER" \
  <hardened-image>@sha256:$PINNED_DIGEST

cosign download attestation <hardened-image>@sha256:$PINNED_DIGEST | \
  jq -r '.payload' | base64 -d | jq '.predicate'

Verify the SLSA Build L3 provenance and the CycloneDX SBOM as a build-pipeline gate.

Stage the Rollout: Dev → Staging → Canary → Blue-Green → 100%

Stage Traffic share Pass criteria Rollback signal
Dev Internal only App boots; smoke test passes; image scan shows < N Critical CVEs Any failure
Staging 0% prod Full integration suite passes; performance regression < 5% on p99 latency Any failure
Canary 1–5% prod 24 h with error rate < pre-migration baseline; no new CVEs introduced Error-rate delta > 0.5%, new Critical CVE introduced, or p99 regression > 10%
Blue-green 100% prod, blue still warm 24 h on green; rollback to blue is one command Any incident in the first 24 h
100% (blue retired) 100% prod 7 days on green, no incidents, scan delta confirmed (post-cutover; rollback now requires CI re-run)

Keep the previous image tagged and pinned by digest until the new image has been at 100% production traffic for at least seven days. Rollback is then a one-line kubectl rollout undo deployment/<service> (or your CD-tool equivalent).

When to Use AI Migration Agents

Docker's Gordon AI assistant and Chainguard's Guardener convert a single Dockerfile's FROM line and best-guess the supporting USER, WORKDIR, and COPY --from= adjustments in seconds. For a stateless single-language service, this is faster than a human.

They do not know your compliance scope, your admission-control policies, your custom CA bundle, or your blast-radius score. They are useful inside the staging-gate of an already-planned rollout, not as the planner. Every AI-migrated Dockerfile must be reviewed and digest-pinned by a human before the canary opens. I have used Gordon as a Dockerfile starting point on three internal migrations and re-written portions of every output before it shipped.

Update CI/CD Pipelines and Registries

Update GitHub Actions, GitLab CI, Jenkins, or Buildkite to:

  1. Verify the hardened-image signature with cosign verify before docker build.
  2. Re-attest a fresh CycloneDX SBOM at build (syft <image> -o cyclonedx-json | cosign attest --predicate - --type cyclonedx <image>).
  3. Two-engine scan (Trivy + Grype, or Trivy + Snyk) and fail the build on Critical or unfixed High.
  4. Push to the registry with the digest tag explicit.

Configure the registry as a pull-through mirror of the hardened-image vendor's registry. Artifactory, Harbor, ECR, and GAR all support this. The pull-through mirror is the single biggest "speed of pulls" win.

For workloads that need a custom hardened image with extra packages, Image Creator builds a private variant in your registry without breaking provenance.

4. After the Migration: Validating Your Security Posture and Staying Protected

Validating the migration means proving, with numbers, that you reduced CVE count, attack surface, and Mean Time to CVE. Then enforcing the win at admission so future CI runs cannot regress. Then producing an audit-ready evidence pack that maps each control to NIST SP 800-190, FedRAMP, FIPS 140-3, STIG, and CIS Docker Benchmark.

Most teams declare victory after the cutover and lose the entire benefit within six months because no admission-control policy prevents the next CI run from FROM ubuntu:24.04-ing back into the cluster. Validation is the half of the migration nobody publishes. It is also the half that keeps the migration ROI compounding instead of decaying.

Measure the Before/After—the Four KPIs

KPI Before (python:3.13) After (dhi.io/python:3.13) Delta
CVE count (Critical + High + Medium + Low) 1 H + 5 M + 141 L + 2 unspec 0 / 0 / 0 / 0 -100%
Total package count 610 80 -87%
Image size 412 MB 35 MB -91%
Mean Time to CVE (avg days from disclosure to remediated image in production) 47 d 5 d -89%

The first three rows come directly from the Docker DHI quickstart docker scout compare dhi.io/python:3.13 --to python:3.13 output. These are the same numbers Google's AI Overview already extracts for docker hardened images.

In regulated environments, this scorecard is the audit deliverable. It lines up against FedRAMP RA-5 (Vulnerability Monitoring and Scanning) and NIST SP 800-190 §4.1.1 (Inventory). I have walked this exact table into a third-party FedRAMP assessment and watched the assessor close out RA-5 evidence in under ten minutes. That is the kind of meeting that justifies the entire migration to leadership.

Enforce in Admission Controllers

Cluster-side policies in Kyverno, OPA Gatekeeper, Connaisseur, Ratify, or Portieris reject any pod whose image is unsigned, missing an attached SBOM, carrying a Critical CVE older than your SLA, pulled by tag instead of digest, or sourced from a registry not on the allow-list:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-hardened-base-images
spec:
  validationFailureAction: enforce
  rules:
    - name: only-allow-listed-registries
      match:
        any:
          - resources:
              kinds: [Pod]
      validate:
        message: "Images must come from an allow-listed hardened-image registry."
        pattern:
          spec:
            containers:
              - image: "dhi.io/* | cgr.dev/chainguard/* | <your-private-mirror>/*"
    - name: require-image-digest
      match:
        any:
          - resources:
              kinds: [Pod]
      validate:
        message: "Images must be pinned by digest."
        pattern:
          spec:
            containers:
              - image: "*@sha256:*"

AWS EKS image security best practices is the upstream authority for this pattern. Every team I onboard to admission control complains about Day-1 friction, then never wants to roll back because the cluster is finally enforcing the standard the platform team has been writing into READMEs for years.

Automate Base-Image Rebuilds

Without automation, the vendor's 7-day patch SLA is wasted because the new digest never reaches CI. A Renovate config that auto-bumps Dockerfile digests:

{
  "extends": ["config:recommended"],
  "docker": { "enabled": true },
  "regexManagers": [
    {
      "fileMatch": ["(^|/)Dockerfile$"],
      "matchStrings": [
        "FROM\\s+(?<depName>[^@:]+)(:(?<currentValue>[^@\\s]+))?@(?<currentDigest>sha256:[a-f0-9]{64})"
      ],
      "datasourceTemplate": "docker"
    }
  ]
}

After every base-image bump: rebuild → re-scan → re-sign → re-attest → canary → 100%, through the same gates as §3.

Map Base-Image Controls to Compliance Frameworks

Framework Control Base-image evidence
NIST SP 800-190 §4.1.1 Inventory CycloneDX SBOM attached; verified by cosign download attestation
NIST SP 800-190 §4.3.2 Capability minimization Dockerfile shows no shell, no package manager; docker history confirms
FedRAMP RA-5 Vulnerability Scanning Hardened image SLA (≤7 days for Critical) beats the FedRAMP 30-day window per FedRAMP Container Vuln Scanning Reqs
FedRAMP CM-6 Configuration DoD STIG-aligned variant in use; baseline mapped to CIS Docker Benchmark v1.6 §4.1–§4.6
FIPS 140-3 Module validation FIPS-validated base image variant in use (e.g., OpenSSL CMVP module #4856)
DISA STIG Container Platform STIG V1R3 STIG-ready image variant + SCAP-validated scan output (OpenSCAP)
CIS Docker Benchmark v1.6 §4.1–§4.6 (Image build) Single-purpose, USER directive, no secrets, content trust, COPY > ADD, hadolint pass
CISA KEV KEV catalog Audit table from §1 shows zero KEV CVEs in any production image

Hardened images move you from firefighting unbounded CVE counts to managing a small, contractual queue. A new CVE alert becomes a workflow: pull the image's VEX, check "not affected", and if affected, check the vendor's published patch ETA. If past SLA, escalate.

MITRE ATT&CK for Containers technique T1611 (Escape to Host) explicitly assumes the attacker uses whatever post-exploitation tooling sits on disk. An image without a shell, without a package manager, and without compilers shrinks that toolkit by default.

A final honest caveat: a hardened base-image migration reduces build-time attack surface and produces audit evidence. It does not replace runtime threat detection, network policy, secrets management, or EDR on the host node. Treat the hardened base image as the foundation, not the entire stack. Treat the admission-control policy in §4.2 as the only thing standing between your migration win and a six-month silent regression.

How Minimus Approaches the Migration

Minimus builds container images directly from upstream source. Minimus provides signed artifacts, CycloneDX SBOM support, and stated remediation SLAs for Critical CVEs (refer to Minimus's source materials for the exact SLA conditions and timing). Minimus positions its images for compliance with frameworks such as NIST SP 800-190, FIPS 140-3, FedRAMP, STIG, CIS, and PCI DSS v4.0, with the same caveats and conditions used in its source materials. The migration produces audit-friendly evidence from the start, not month six.

The build-from-source approach (rather than stripping packages out of an existing distribution after the fact) results in substantially reduced CVE counts at pull time, near-zero in many cases. Drop-in compatibility with Debian, Ubuntu, RHEL, and Alpine makes adoption a low-friction Dockerfile change in many cases. Minimus images are compatible with common scanners such as Trivy, Grype, Snyk, Docker Scout, AWS Inspector, and Anchore.

For workloads that need extra runtime packages, Image Creator builds a private variant in your registry under the same provenance and SLA terms. How to move from distribution-based images to distroless is the dedicated distroless-only companion to this guide. Verification commands, SBOM retrieval, and admission-policy examples live in the Minimus documentation.

Browse the Minimus image gallery at images.minimus.io to compare current public base images with their hardened equivalents before you start the audit phase.

FAQ  Migrating to Hardened Container Images

How long does it take to migrate from public to hardened container images?

A first-pass, single-service migration typically takes 1–3 days end-to-end if you already have a CI/CD pipeline, automated scanning, and a digest-pinning convention in place. A whole-fleet migration across 20–50 services usually runs 6–12 weeks, split across the four phases: audit (1–2 weeks), mapping (1 week), staged rollout in waves of 5–10 services (4–8 weeks), and post-migration validation including admission-control rollout (2–3 weeks).

Is hardening the same as patching?

No. Hardening is the architectural choice to ship only the components your workload actually runs (no shell, no package manager, no compilers, no curl) and to source those components from a signed, SBOM-attested supply chain. Patching is the operational cadence with which you re-pull, re-build, and re-deploy after a CVE drops. A hardened base image gives you both. The architecture is set at pull time, and the patch SLA (typically 24–48 hours for Critical CVEs) is set by the vendor.

What are the options for hardened container images in 2026?

The eight production-grade hardened-image families currently shipping in 2026 are: Docker Hardened Images (free under Apache 2.0 since December 2025), Chainguard Containers (Wolfi-based, free :latest and :latest-dev tier), Wolfi-direct (the open distro under Chainguard's tooling), Red Hat Universal Base Image Micro 9, Canonical Chiselled Ubuntu 24.04, BellSoft Alpaquita (JVM-tuned), Iron Bank (US Air Force / DoD), and source-built minimal alternatives. They differ on minimization technique, libc, signing/SBOM/VEX, patch SLA, and compliance posture.

Is there any reason NOT to migrate to a hardened container image?

Two valid reasons to stay on a public image. First, the workload depends on a shell, package manager, or interactive tool at runtime (e.g., a CI runner image, a developer sandbox, a WordPress install that expects apt-get at startup). Most of these can be re-architected with a multi-stage build. Second, you have no automated way to bump base-image digests, in which case the SLA you would buy from a vendor never reaches production. Address both before migrating; do not skip the migration to avoid them.

How do I find the base image of an existing Docker image?

Inspect the image with docker history --no-trunc <image> or docker inspect <image>, or use dive to walk the layers visually. The lowest layer is the base. For images that ship an SBOM, the base distribution and version are recorded explicitly inside the SBOM document. Pull it with cosign download attestation <image>@sha256:<digest> and parse the predicate field. This is the first command of the audit phase.

Can I roll back if a hardened image breaks production?

Yes, provided you keep the previous image tagged and pinned by digest until the new image has been at 100% production traffic for at least seven days. The rollback is then a one-line kubectl rollout undo deployment/<service> (or your CD-tool equivalent). The blue-green pattern in §3.5 is the canonical safety net: blue (the public-image build) stays warm for 24 hours after green (the hardened-image build) hits 100%, so a regression caught in the first day is one command away from recovery.

Migrating from public to hardened container images is one project, four phases (audit, map, stage, validate) and one discipline: every later CI run must inherit the hardened foundation, or the migration loses its compounding effect inside six months. Run the audit table, build the mapping table, ship the staged rollout, and enforce the win at admission. The next CVE that drops becomes a triage ticket, not an incident.

Yael Nardi
CBO
Sign up for minimus

Avoid over 97% of container CVEs

Access hundreds of hardened images, secure Helm charts, the Minimus custom image builder, and more.