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

Ori Zerah
Ori Zerah
Sep 07, 2026 | 12 Minutes
Migrate from public base images to hardened container images: a step-by-step guide (2026)

A hardened container image is a minimized base image designed to reduce unnecessary packages and improve supply-chain evidence. Depending on the provider and tier, that evidence may include signatures, SBOMs, provenance attestations, VEX data, and published vulnerability-remediation targets. The base image is the one piece of a containerized application many teams inherit without reviewing closely.

It also influences how much attack surface every higher control, from admission policies to runtime monitors, has to defend.

In many regulated cloud environments, the same migration pattern shows up repeatedly. The hard part is rarely just the FROM-line swap. It is producing the audit table that justifies the swap, and the CI/admission policy that prevents future drift back to public, unsigned, or stale images.

This guide walks a four-phase migration from public base images to hardened equivalents: audit, map, stage, and validate.

Key Takeaways

  • Migrating from public to hardened container images is a four-phase project: audit, map, stage the rollout, and validate. Hardened images can dramatically cut CVE exposure - Echo customers report roughly 90% fewer container vulnerabilities after switching, with CVE counts dropping to zero by design.
  • The gap between a public base image and a hardened one is large: upstream images such as python, node, and redis routinely ship with hundreds to thousands of known CVEs, while a hardened equivalent is built to carry none. Always re-run the comparison against the exact tags and platforms in your own environment, since counts shift as images and CVEs change.
  • The audit phase is where many migrations either gain or lose credibility. Public community images commonly accumulate hundreds of CVEs and hundreds of components, and simple package updates typically clear only a small fraction of them — which is why re-basing on a hardened image beats endless patching.
  • There is no single right hardened image for every workload. Options range across minimal distroless-style images, source-built minimal alternatives, and fully managed CVE-free images such as Echo. Compare them on minimization technique, libc, signing/SBOM/VEX support, remediation commitments, and compliance posture.
  • The migration only sticks if it is enforced in CI and admission control. Without policy for trusted registries, signatures, SBOM or attestation requirements, digest pinning, and image freshness, future builds can drift back to public or stale images.

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

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

The output is a migration backlog prioritized by risk, not by whichever Dockerfile is easiest to edit first. This aligns with the inventory and scanning discipline in NIST SP 800-190 and with Chainguard's 2024 report, which measures CVE exposure by image ecosystem.

Many organizations discover during this step that Dockerfile references, running workload images, and registry contents are tracked in separate systems. The goal is to create one source of truth for the base images actually running in production.

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

Run a registry-wide and cluster-wide enumeration, not a single image scan:

  • Across registries: use crane catalog, crane manifest, or gcrane to enumerate image:tag pairs where the registry supports it.
  • Across running clusters: use kubectl get pods --all-namespaces -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\n' | sort -u.
  • Per image: use docker history --no-trunc <image>, docker inspect <image>, or dive to inspect layers and infer the base distribution where the image metadata supports it.
  • For images that ship an SBOM or attestation: parse the SBOM with a tool such as Syft, or retrieve attached attestations with Cosign where the publisher supports them.

The output is a single table with one row per unique base image. Include columns for production workload count, first-introduced date, last upstream rebuild, registry source, and whether the row is backed by scan/SBOM evidence.

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

The numbers your audit produces may not be small. Official community base images frequently carry hundreds of CVEs and hundreds of components, and a large share of image scans surface vulnerabilities for which a newer, more secure base image already exists. Score the real exposure before deciding what to migrate first.

Snyk has reported that 44% of Docker image scans by Snyk users had known vulnerabilities for which newer and more secure base images were available.

Score by CVE blast radius: severity multiplied by the number of workloads inheriting the image. Do not score by raw CVE count alone. A High CVE in a base image used by 40 services may deserve more urgent treatment than a Critical finding in a single internal tool.

This rubric is meant to prevent a common prioritization failure: treating a single-service Critical finding as automatically more urgent than a High finding inherited by many production workloads.

Step 3: Produce a Prioritized Migration Backlog

The following table is illustrative. Replace the values with scanner output from your own registries, clusters, and SBOMs before using it in an audit package.

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 security and platform-engineering audiences. It is also the baseline you measure against after migration.

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

Mapping each public base image to a hardened equivalent is a workload-aware decision. The same image can map differently depending on libc requirements, package-manager behavior, FIPS needs, STIG evidence, patch commitments, or available subscription tiers.

Options span minimal distroless-style images, source-built minimal alternatives, and fully managed CVE-free images such as Echo. Compare them across minimization technique, libc, signing and SBOM support, VEX availability, patch commitments, and compliance posture.

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 Common migration target; check each vendor's Node image guidance before swapping
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 Official Windows base OS images come from Microsoft Container Registry; custom images layer on top
DoD / FedRAMP-regulated workload Iron Bank (repo1.dso.mil/dsop), Chainguard FIPS variants (700+ variants using NIST-validated cryptographic modules), DHI Select/Enterprise FIPS/STIG Use vendor evidence to support your authorization boundary; image choice alone does not grant FedRAMP authorization

Pick candidates that satisfy your compliance posture first, then optimize for patch commitment, operational fit, and image size.

Docker's December 17, 2025 press release says DHI became free and fully open source under the Apache 2.0 license, with DHI Enterprise available for customization, compliance variants, and SLA-backed updates.

For regulated workloads, compare each vendor's remediation commitments against the FedRAMP vulnerability-management requirements that apply to your authorization boundary and POA&M process.

What Breaks During Migration: The 12 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

These are common migration failure modes to test before canary. CA-certificate path issues are especially easy to misdiagnose because the symptom often looks like an upstream TLS or API outage.

3. Making the Switch: A Safe, Staged Rollout

A safe rollout is a staged pipeline discipline. Pick a low-risk pilot workload, use multi-stage builds to keep upstream tooling out of production, swap one base at a time, pin the new image by digest, run it through dev -> staging -> canary -> blue-green -> 100%, and keep a written rollback plan for every wave.

A practical sequencing rule is to start with application images before attempting to standardize every shared base image. This reduces coordination overhead and lets service owners validate the change in their own CI/CD path.

Pick the Lowest-Risk Pilot Workload First

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

A workload is a poor pilot when it is stateful, runs sparse cron or batch workloads, lacks automated rollback, or carries a near-term customer-facing SLA risk.

A gateway or API service can be a strong pilot when it is stateless, well-instrumented, and easy to canary. Avoid starting with stateful workloads or sparse test coverage, because early failures can slow adoption across later waves.

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 usually be different. Compilers, package managers, and shell utilities belong in the builder stage unless the workload truly needs them at runtime.

Pin by Digest, Not Only by Tag

FROM nginx:1.27 may pull different bytes over time if the tag is rebuilt. The same is true of hardened-image tags. Pin to an immutable content digest:

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

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

Renovate can update Docker digests through pull requests, which makes base-image rebuilds visible instead of silently changing behind a mutable tag.

Verify Signature, SBOM, and Provenance Before the Build Runs

Use the verification workflow supported by your chosen vendor. A common Cosign pattern is to verify the image signature and, when attestations are expected, verify the attestation with the correct identity, issuer, and predicate policy before inspecting its contents:

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

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

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

Sigstore documents cosign verify-attestation for attestation verification. Treat cosign download attestation as inspection, not proof of trust by itself. Verify provenance and SBOM evidence as a build-pipeline gate when the vendor publishes those attestations.

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 pinned by digest until the new image has been at 100% production traffic for an agreed observation window.

Update CI/CD Pipelines and Registries

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

  1. Verify the hardened-image signature before docker build where the vendor supports signatures.
  2. Generate or retrieve a fresh SBOM and attach it to the built image.
  3. Scan with at least one supported scanner, and cross-check high-risk images with a second engine.
  4. Fail the build on Critical or unfixed High findings according to your policy.
  5. Push to your registry with digest references captured in deployment metadata.

Where your registry supports it, configure a pull-through cache or mirror for the hardened-image vendor registry. Artifactory, Harbor, ECR, and Google Artifact Registry have registry proxy or remote-cache patterns, but the exact setup and supported upstreams vary.

For workloads that need extra runtime packages, follow the documented Echo process for requesting a new image type or altered packages. Confirm provenance, support, and SLA terms for any private or customized variant in your Echo agreement before relying on those terms in an audit package.

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

Validation means proving with evidence that you reduced CVE count, package count, image size, and remediation latency. Then it means enforcing the win in CI and admission control so future builds cannot drift back to public, unsigned, or stale images.

Most teams should not declare victory after the cutover alone. Without policy enforcement, the next rebuild can accidentally reintroduce a public base image or mutable tag.

Measure the Before/After 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%

Use the Docker quickstart output as an example, and re-run the comparison against the exact tags and platforms in your environment.

For regulated environments, pair this scorecard with scanner output, SBOMs, attestations, image identifiers, scan dates, and POA&M evidence so assessors can trace each claim back to a source artifact.

Enforce in Admission Controllers

Cluster-side policies in Kyverno, OPA Gatekeeper, Connaisseur, Ratify, Portieris, or similar tools can reject pods that do not meet your policy: untrusted registry, missing digest, missing signature, missing attestation, stale scan evidence, or disallowed CVE severity.

Do not copy a placeholder policy into production. For Kyverno, use tested verifyImages rules with explicit static imageReferences, verifyDigest, and the correct attestors for your signing model. For registry allow-lists, use a tested validation policy rather than a pipe-separated string inside a single image pattern.

AWS EKS image-security guidance documents this pattern: use minimal images, SBOMs, attestations, vulnerability scanning, and admission controllers to reject images that do not meet policy.

Automate Base-Image Rebuilds

Without automation, a vendor remediation target does not help production if the new digest never reaches CI. For ordinary Dockerfiles, start with Renovate's built-in Docker support and digest pinning:


{
  "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, scan, sign, attest, canary, and roll out through the same gates as the initial migration.

Map Base-Image Controls to Compliance Evidence

Framework Control Base-image evidence
NIST SP 800-190 §4.1.1 Image vulnerabilities Image scan report and SBOM/package inventory attached; attestation verified with cosign verify-attestation where supported
NIST SP 800-190 §4.1.2 Image configuration defects Image configuration evidence shows non-root execution, minimized tooling, and no unnecessary package manager in the runtime image
FedRAMP RA-5 Vulnerability Scanning SLA-backed Critical/High remediation can help meet FedRAMP vulnerability-management windows when paired with rebuild, scan, and redeploy automation
FedRAMP CM-6 Configuration STIG-ready or STIG-hardened variant in use; configuration baseline validated against the applicable benchmark or 3PAO-approved hardening profile
FIPS 140-3 Module validation FIPS-validated cryptographic module in use, with the exact CMVP certificate matched to the selected image and FIPS standard
DISA STIG Applicable DISA STIG or vendor GPOS SRG profile STIG-ready or STIG-hardened image variant plus OpenSCAP results for the relevant profile
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 unbounded CVE firefighting to a smaller, evidence-backed queue. A new CVE alert becomes a workflow: check the SBOM, review VEX or advisory data where available, confirm whether the image is affected, and track the vendor's remediation target.

MITRE ATT&CK T1611 describes container escape-to-host techniques, and its mitigations include removing unnecessary tools and software from containers. Images without shells, package managers, and compilers reduce the tooling available after compromise, but they do not eliminate runtime escape risk.

A hardened base-image migration reduces build-time attack surface and improves audit evidence. It does not replace runtime threat detection, network policy, secrets management, or host/node protection. Treat the hardened base image as the foundation, not the entire stack. Treat admission control and CI policy as the mechanisms that keep the migration from drifting back to public, unsigned, or stale images.

How Echo Approaches the Migration

Echo backs its images with an enterprise SLA: Critical and High CVEs are triaged within 24 hours and fixed in up to 7 days, with images continuously rebuilt so you're always running clean versions. Every image is built on Echo's SLSA L3 build infrastructure, signed and attested, and delivered with an SBOM, provenance, and VEX data. See the container images product page for current SLA terms before relying on them in an audit package.

Evaluate compatibility per image family, libc, package manager, UID/GID, and entrypoint before treating a replacement as drop-in. Validate scanner output in your pipeline with the exact tools you rely on, such as Trivy, Grype, Snyk, Docker Scout, AWS Inspector, or Anchore.

Echo is a drop-in replacement for the open-source container images, and you can migrate to Echo by simply changing the FROM in your Docker File.

FAQ: Migrating to Hardened Container Images

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

Timelines vary by test coverage, compliance scope, native dependencies, and rollout automation. A low-risk single service can often be migrated in days, while a fleet migration should be planned in waves that include audit, mapping, staged rollout, and post-migration policy enforcement.

Is hardening the same as patching?

No. Hardening is the architectural choice to ship only the components your workload needs and to preserve supply-chain evidence such as signatures, SBOMs, and provenance where available. Patching is the operational cadence with which you rebuild and redeploy after vulnerabilities are fixed upstream. Remediation targets vary by vendor, severity, subscription tier, and upstream fix availability.

What are the options for hardened container images in 2026?

Common options in 2026 include Echo, Chainguard, Google distroless images, and source-built minimal alternatives. They differ on minimization technique, libc, signing/SBOM/VEX support, remediation commitments, and compliance posture.

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

There are valid reasons to delay a migration. A workload may depend on a shell, package manager, or interactive tool at runtime, such as a CI runner image, a developer sandbox, or software that expects to install packages at startup. A second reason is lack of automation: if you cannot bump digests, rebuild, scan, and redeploy reliably, a vendor remediation target will not reach production.

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

Start with docker history --no-trunc <image> or a layer-inspection tool such as dive to understand the image layers. Then cross-check docker inspect labels/config, SBOM or provenance metadata, and publisher documentation. docker inspect can expose useful clues, but it usually cannot reliably identify the original base image by itself.

Can I roll back if a hardened image breaks production?

Yes, if you keep the previous image pinned by digest and available until the new image has passed its observation window. The rollback may be a kubectl rollout undo deployment/<service> or the equivalent in your deployment tool. Blue-green and canary patterns are useful because the previous public-image build remains available while the hardened-image build proves itself in production.

Migrating from public to hardened container images is one project with four phases: audit, map, stage, and validate. Run the audit table, build the mapping table, ship the staged rollout, and enforce the result in CI and admission control. The next CVE should become a triage workflow, not a production surprise.

What are the 7 blind spots in your vulnerability scans?

Discover when "0 vulnerabilities" doesn't actually mean you're clean.

Read now →

Ready to eliminate vulnerabilities at the source?