container-security
devsecops
kubernetes
cloud-engineering
docker
trivy
supply-chain-security

47 Known CVEs Just Deployed to Production

Why Container Image Scanning Isn't Optional Anymore

A developer pulls a base image from Docker Hub, builds their app on top, and ships it. Nobody checks what's inside that base layer. Turns out it's running an OpenSSL version with 3 critical vulnerabilities. This happens more than you think. 87% of container images in production carry high-severity CVEs. Here's how to fix it.

How It Happens

A developer pulls node:20 from Docker Hub. Builds the app on top. Pushes to the registry. CI runs unit tests, linting, maybe even integration tests. Everything passes. Ship it.

Nobody checked what's inside that base layer. That node:20 image is built on Debian Bookworm, ships with 239 packages your app never calls, and carries an OpenSSL version with 3 critical vulnerabilities. A libcurl with 2 more. A zlib with a known buffer overflow. Your application code is clean. Your container is not.

87%

of production images have high-severity CVEs

239

avg vulnerabilities in Debian-based images

120+

days since most Docker Hub images were updated

This isn't a hypothetical. This is Monday morning for most teams shipping containers.

Why “Just Update” Doesn't Work

The intuitive fix is to run apt-get update && apt-get upgrade in your Dockerfile. But Chainguard's research shows that updating OS packages in official Docker Hub images only reduces vulnerability counts by less than 6%. Before updates: 239 vulnerabilities. After updates: 225 vulnerabilities.

Why? Because 98% of vulnerabilities in Debian-based Docker images come from OS packages that are part of the distribution itself. They're not bugs in your code—they're bugs in the 200+ packages your runtime never calls but that ship in the base layer anyway. Updating within the same distribution just gives you the same bloated package set with minor patch bumps.

“You can't patch your way out of a bloated base image. The packages aren't broken—they shouldn't be there in the first place.”

The 4-Step Shift-Left Playbook

1

Scan in CI/CD, Not in Production

Catch CVEs Before They Ship

Add Trivy or Grype as a pipeline step. Fail the build on critical and high-severity CVEs. This takes 30 seconds to set up and saves you 3 AM incident calls. If an image has a known critical vulnerability, it doesn't get pushed to the registry. Period.

GitHub Actions — Trivy

- name: Scan image for CVEs
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE }}
    format: 'table'
    exit-code: '1'
    severity: 'CRITICAL,HIGH'
    ignore-unfixed: true

GitLab CI — Grype

scan_image:
  stage: security
  image: anchore/grype:latest
  script:
    - grype $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
        --fail-on critical
        --only-fixed
  allow_failure: false

Trivy

All-in-one scanner: vulnerabilities, misconfigurations, secrets, IaC, and license compliance. Generates SBOMs. Maintained by Aqua Security. VS Code extension available.

Grype

Focused vulnerability scanner with excellent accuracy and minimal false positives. Smaller database updates. Pairs with Syft for SBOM generation. Maintained by Anchore.

Also consider: Docker Scout (built-in to Docker Desktop, generates SBOMs automatically) and Kubescape (CNCF incubating project, Kubernetes-native scanning with admission controller support).

2

Pin Your Base Images by Digest

Immutable References, Not Mutable Tags

Never use latest. Never use bare tags like node:20. Pin to a specific SHA256 digest. Tags are mutable—someone can push a different image to the same tag at any time. A digest is a cryptographic hash of the image content. If a single byte changes, the digest changes. This is your defense against supply chain attacks where malicious images get pushed to the same tag.

Bad — Mutable tag

FROM node:20-alpine

Good — Tag + digest for readability and security

FROM node:20-alpine@sha256:2f3b7a...c9d4e1

Get the Digest

docker buildx imagetools inspect \
  node:20-alpine --format \
  '{{ .Manifest.Digest }}'

Enforce in Kubernetes

Use Kyverno or OPA Gatekeeper admission controllers to reject pods using mutable tags. Block :latest and require digest references.

3

Use Distroless or Minimal Base Images

Fewer Packages = Fewer CVEs

The most effective way to reduce CVEs isn't scanning—it's eliminating the packages that carry them. A standard Debian-based image ships with ~300 packages. A distroless image ships with your binary and its runtime dependencies. Nothing else. No shell, no package manager, no curl, no wget, no attack surface.

Alpine

~5 MB

musl-based, minimal, widely supported. Good first step.

Distroless

~2 MB

Google's distroless. No shell, no pkg manager. Production hardened.

Chainguard

~0 CVEs

Wolfi-based, rebuilt nightly. 97.6% CVE reduction vs Debian.

Docker Hardened

95%

fewer vulns. 1,000+ images, now free and open source (Dec 2025).

Multi-stage build — Build fat, ship thin

# Stage 1: Build with full toolchain
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build

# Stage 2: Ship with minimal runtime
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app
COPY --from=builder /app/node_modules /app/node_modules
WORKDIR /app
CMD ["server.js"]

Tradeoff: Distroless images have no shell. You can't exec into them for debugging. Use ephemeral debug containers in Kubernetes (kubectl debug) instead.

4

Automate Periodic Re-Scans

Images Drift. Scan Continuously.

An image that was clean last month might not be today. New CVEs are disclosed daily. If your image was built 90 days ago, it's likely accumulated new vulnerabilities since then. Schedule weekly scans of all running images and fail deployments that reference stale, unscanned images.

Scheduled CI Scans

# GitHub Actions cron
on:
  schedule:
    - cron: '0 6 * * 1'  # Weekly Monday 6AM
  workflow_dispatch: {}

jobs:
  rescan:
    runs-on: ubuntu-latest
    steps:
      - name: Scan running images
        run: |
          for img in $(kubectl get pods -A \
            -o jsonpath='{..image}' | tr ' ' '\n' \
            | sort -u); do
            trivy image "$img" --severity CRITICAL,HIGH
          done

Run a cron job in CI that scans every unique image running in your clusters.

Kubernetes-Native Scanning

Deploy Kubescape as an in-cluster operator. It continuously scans running workloads against CIS benchmarks, NSA-CISA guidelines, and MITRE ATT&CK frameworks.

Pair it with an admission controller that blocks pod creation if the image hasn't been scanned or fails policy checks. This is your last line of defense before deployment.

Remediation SLA benchmark: Chainguard reports average remediation of under 20 hours for critical CVEs, with 97.6% fixed within 2 days. Set your own SLAs: 72 hours for critical, 7 days for high, 30 days for medium.

Build an Approved Base Images Registry

Pre-Scanned. Pre-Hardened. Regularly Updated.

This is the highest-leverage action you can take. Instead of letting every developer pick their own base image from Docker Hub, maintain a curated internal registry of approved base images. Your developers get fast, reliable builds. Your security team gets peace of mind. Everybody wins.

1

Curate Your Golden Images

Select base images for each language runtime your org uses: Node.js, Python, Java, Go, .NET. Start with Alpine or distroless variants. Strip unnecessary packages. Add only what your workloads require.

2

Automate Nightly Rebuilds

Set up a CI pipeline that rebuilds every golden image nightly from pinned upstream digests. Scan each rebuild with Trivy. Only promote images that pass with zero critical CVEs to the "approved" tag.

3

Sign and Attest

Sign every approved image with Cosign (Sigstore). Attach an SBOM attestation. This creates a verifiable chain of trust from source to deployment that admission controllers can validate.

4

Enforce via Admission Control

Configure Kyverno or OPA Gatekeeper to only allow images from your internal registry with valid signatures. Reject anything from Docker Hub, ECR Public, or unsigned registries in production namespaces.

5

Publish an Internal Catalog

Create a simple internal page listing your approved images, their digests, last scan date, and CVE count. Make it easy for developers to find the right base image without hunting through Docker Hub.

The Supply Chain Threat Is Real

Public Registries Are Silent Risk Multipliers

Since early 2025, attacks on build systems, unverified registry pulls, and misconfigured Kubernetes deployments have significantly increased. Public container registries (Docker Hub, ECR Public, GHCR) now host crypto mining binaries, malware, typo-squatted images, and exposed secrets—often pulled automatically by CI/CD pipelines without any human review.

Supply chain attacks are projected to cost businesses $60 billion globally in 2025—triple the impact from 2021. The container image is the most under-audited artifact in most CI/CD pipelines.

Attack Vectors

Typo-squatted image names (ngnix instead of nginx)
Compromised upstream images via tag mutation
Crypto miners embedded in popular image forks
Exposed secrets (API keys, tokens) in image layers
Malicious packages in language-specific dependencies

Defenses

Pin images by SHA256 digest, not tag
Sign images with Cosign/Sigstore
Generate and verify SBOMs
Use private registries for production
Enforce admission policies blocking unsigned images

Scanner Comparison

ToolTypeScansSBOMBest For
TrivyOpen sourceImages, IaC, FS, repos, secretsYes (SPDX)All-in-one CI/CD scanning
GrypeOpen sourceImages, FS, SBOMsVia SyftAccurate, minimal false positives
Docker ScoutFree tierImages via Docker Desktop/HubYesDocker-native workflows
KubescapeOpen source (CNCF)Images, K8s configs, runtimeVia GrypeKubernetes-native security
Snyk ContainerCommercialImages, code, IaCYesEnterprise integrations, fix PRs

What NOT to Do

Don't Scan Only at Build Time

New CVEs are disclosed daily. An image that was clean at build time can have critical vulnerabilities discovered a week later. You need continuous re-scanning of running images, not just build-time gates.

Don't Ignore "Unfixed" CVEs

Some scanners report CVEs with no available fix. Don't just suppress them and move on. Track them, set SLAs, and use them as leverage to move to base images where fixes ship faster (Alpine, Chainguard).

Don't Treat Scanning as a Gate Without Action

A scan that generates a 200-line report nobody reads is security theater. Set clear severity thresholds, auto-fail builds on critical CVEs, and route findings to owners with remediation deadlines.

Don't Use "latest" in Production

The :latest tag is a mutable pointer. It can change under you between builds, between environments, between deploys. In 2026, using :latest in production is negligence, not convenience.

Don't Skip the SBOM

Without a Software Bill of Materials, you can't answer "are we affected?" when the next Log4Shell drops. Generate SBOMs for every image, store them alongside the image, and make them queryable.

Your Action Plan

Implement This Week

Security isn't a gate—it's a guardrail. Build it into the road, not at the end of it. Here's your concrete implementation plan:

This Week's Checklist

Add Trivy or Grype to one CI pipeline. Set it to fail on CRITICAL severity. Takes 5 minutes.

Audit your Dockerfiles for :latest or bare tags. Replace with tag@sha256:digest references.

Pick one high-traffic service and switch its base image from Debian to Alpine or distroless. Measure the CVE delta.

Set up a weekly cron scan of all images running in your staging cluster.

Generate an SBOM for your most critical production image using Syft or Trivy.

Document your team's approved base images in a shared wiki/README. List the image, digest, last scan date, and CVE count.

Add a Kyverno or OPA Gatekeeper policy to block :latest tags in your staging namespace.

Schedule a 30-minute team meeting to review scan results and set remediation SLAs.

Key Takeaways

87% of container images in production carry high-severity CVEs. Most come from bloated base image packages your app never uses.

Updating OS packages in Debian-based images only reduces CVE count by ~6%. You need a fundamentally different base image, not a patch.

Scan in CI/CD, not in production. Fail builds on critical CVEs. Trivy and Grype both integrate in under 5 minutes.

Pin base images by SHA256 digest, not mutable tags. Never use :latest in production. Enforce via admission controllers.

Use distroless, Alpine, Chainguard, or Docker Hardened Images. Fewer packages = fewer CVEs = smaller attack surface.

Automate weekly re-scans of running images. New CVEs are disclosed daily; build-time scans alone are not enough.

Build an approved base images registry: pre-scanned, signed with Cosign, rebuilt nightly, enforced via admission policy.

Generate SBOMs for every image. When the next Log4Shell hits, you need to answer "are we affected?" in minutes, not days.

Supply chain attacks cost $60B globally in 2025. Public registries are silent risk multipliers. Sign, verify, and restrict.

Security Isn't a Gate. It's a Guardrail.

Build it into the road, not at the end of it. Every unscanned image in production is a ticking clock. Start with one pipeline, one scanner, one policy. Then scale.