Skip to content
Back to Blog
Linux & Server9 min read

Docker Hardened Images and VEX: How to Reduce CVE Noise in Container Scans

Getting 200 CVEs per scan and not knowing which ones matter is not a security posture — it is alert fatigue. Docker Hardened Images and the VEX standard let you separate real exploitable risks from noise so your CI gate fails for the right reasons.

Written by Abdul AbrorTechnical Hosting Support Engineer
On this page

You run a container vulnerability scan and get 200 CVEs. Your pipeline turns red. Then you dig in and find that 180 of them are in packages your application never calls, several are unfixable without breaking the base image, and a few are already patched in a way the scanner missed. This is CVE noise — and it is the most common reason teams either ignore scan results entirely or chase false urgency. Docker's Hardened Images and the VEX standard exist to separate real risk from noise, and this guide shows you how to use both practically.

The CVE Noise Problem

Traditional container scanners report every CVE found in every package inside an image, regardless of whether the package is reachable by the running application. A base image like ubuntu:22.04 contains hundreds of packages. A CVE in a library used only by a tool you never run is still reported as a finding.

The result is a vulnerability list that overwhelms developers, causes alert fatigue, and often leads to two bad outcomes: either teams block every deployment on CVE count (slowing delivery for non-issues) or they mark everything as accepted risk without investigation (missing real problems). Neither is good.

What Are Docker Hardened Images?

Docker Hardened Images (DHI), announced by Docker, are a set of curated base images built to minimise both attack surface and CVE count from the start. They share goals with distroless and Chainguard images but are distributed directly through Docker Hub with a commercial support tier.

Key characteristics:

  • Minimal packages: only what the runtime actually needs — no shell, no package manager, no build tools in the final image
  • Signed provenance: signed with Sigstore/cosign for verifiable supply chain integrity
  • Enhanced scanning: Docker integrates Aikido for more accurate reachability analysis (not just raw CVE count)
  • VEX metadata included: the images ship with VEX documents that declare which CVEs are not exploitable in that specific image context

What Is VEX?

VEX stands for Vulnerability Exploitability eXchange. It is an open standard from CISA that allows a software vendor or maintainer to publish a machine-readable statement about whether a specific CVE in their product is exploitable.

VEX statements can declare one of four statuses for each CVE:

  • not_affected — the CVE exists in the image but is not exploitable (most common for base image noise)
  • affected — the CVE is confirmed exploitable; a fix is required
  • fixed — the CVE has been remediated in this version
  • under_investigation — the maintainer is evaluating the impact

When a scanner supports VEX, it can automatically suppress not_affected and fixed findings, dramatically reducing the noise in your scan output without hiding real risks.

Practical Impact: Before and After VEX

A scan of a standard Node.js image might report 80-120 CVEs of varying severity. With VEX suppression applied:

  • CVEs in packages never installed in the runtime layer: suppressed
  • CVEs in packages present but never reachable by the application code: suppressed
  • CVEs in dev-only packages excluded from the production image: suppressed
  • Remaining findings: 5-15 CVEs that are actually relevant to your application

That is the list worth acting on. The rest is noise that previously required manual triage per scan run.

Using Docker Hardened Images in Your Build

DHI uses the same multi-stage pattern as distroless. Here is an example for a Node.js application:

# Build stage
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build

# Runtime stage — hardened minimal image
FROM docker/nodejs-hardened:20 AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
EXPOSE 3000
CMD ["node", "dist/server.js"]

The docker/nodejs-hardened image has no shell, no package manager, and no build tools. It only contains what a Node.js runtime needs to execute. This removes an entire category of CVEs before the scanner even runs.

Scanning with VEX Support: Grype

Grype is an open-source container scanner with native VEX support.

# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

# Scan with VEX file
grype your-image:latest --vex /path/to/image.vex.json

# Output only critical and high findings
grype your-image:latest --fail-on critical

For Docker Hardened Images, the VEX documents are available via Docker Hub alongside the image manifest. Pull them with cosign download attestation to get the vendor-provided suppression list.

Creating Your Own VEX Statements

For images you maintain yourself, you can author VEX documents for CVEs you have investigated and confirmed as not exploitable. The OpenVEX CLI (vexctl) makes this straightforward:

vexctl create \
  --author "[email protected]" \
  --product "pkg:oci/your-image@sha256:abc123" \
  --vuln "CVE-2024-12345" \
  --status "not_affected" \
  --justification "vulnerable_code_not_in_execute_path" \
  > your-image.vex.json

Store VEX documents alongside your Dockerfile in version control. Reviewers can audit every suppression decision, and the file becomes part of your supply chain documentation.

Integrating into CI/CD

A practical CI gate using Grype with VEX suppression (GitHub Actions format):

- name: Scan image with Grype (VEX suppressed)
  run: |
    grype $IMAGE_TAG \
      --vex .vex/image.vex.json \
      --fail-on high \
      --output table

This fails the pipeline only on HIGH or CRITICAL CVEs that are not suppressed by your VEX document. Developers see a short, actionable list instead of 200 findings that numb them to real risks.

Checklist

  1. Migrate production images to a minimal base (Docker Hardened, distroless, or Alpine slim)
  2. Use multi-stage builds to keep build tools out of the final image
  3. Install and run Grype or Trivy against every new image build
  4. Download vendor-provided VEX documents for your base images
  5. Author VEX statements for CVEs you have confirmed as not_affected
  6. Store VEX files in version control alongside the Dockerfile
  7. Add a CI gate: fail only on HIGH/CRITICAL findings not suppressed by VEX
  8. Review and update VEX documents quarterly

Conclusion

CVE count is a misleading metric. A scan showing 5 confirmed, exploitable CVEs is more dangerous than a scan showing 200 CVEs of which 195 are not reachable by your application. Docker Hardened Images reduce the raw count by shrinking the attack surface; VEX reduces the reported count by making exploitability context machine-readable. Together, they give your team a scan output that is actually actionable — fewer findings, higher signal, and a CI gate that fails for the right reasons.

FAQ

Is VEX the same as marking CVEs as accepted risk?

No. Accepted risk means “we know this is a problem and accept the consequence.” VEX not_affected means the vulnerable code path is not reachable in this product. VEX is a technical statement about exploitability, not a risk acceptance.

Do I need Docker Hardened Images to use VEX?

No. VEX is an open standard that works with any image and any compatible scanner. DHI just happens to ship with VEX documents out of the box, reducing the work you need to do. You can author your own VEX statements for any base image.

What scanners support VEX?

Grype has native VEX support. Trivy added VEX support starting in v0.46. Docker Scout integrates Aikido's reachability analysis which serves a similar function.