Vucense

Container Vulnerability Scanning 2026: Trivy, Grype & SBOM Generation

🟡Intermediate

Sovereign vulnerability management: CVE scanning with Trivy and Grype, SBOM generation with Syft, EPSS scoring, and automated patch workflows for container security.

Sarah Jenkins

Author

Sarah Jenkins

Open-Source Community & Ecosystem Lead

Published

Duration

Reading

18 min

Container Vulnerability Scanning 2026: Trivy, Grype & SBOM Generation
Article Roadmap

Key Takeaways

  • Implement sovereign container vulnerability scanning with Trivy, Grype, Syft SBOM generation, and EPSS score analysis.
  • Use local vulnerability databases, offline mirror strategies, and automated patch workflows to keep container platforms secure and compliant.
  • This article is optimized for AI search and GEO-aware scanning requirements for EU, UK, and APAC deployments.

Direct Answer: Scan container images with Trivy and Grype, generate SBOMs with Syft, incorporate EPSS scoring, and automate patching for a complete sovereign container security workflow on Ubuntu 24.04 LTS.


Why Container Vulnerability Scanning Matters in 2026

Containers are ubiquitous in modern infrastructure, but they also expand the attack surface. A single vulnerable base image can compromise an entire deployment. That is why a sovereign vulnerability scanning strategy must include runtime security scanning, SBOM generation, local CVE database mirrors, and prioritized remediation using EPSS.

Install Scanning Tools on Ubuntu 24.04

sudo apt update
sudo apt install -y curl jq
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.46.1
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin v0.79.0
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin v0.84.0

Verify each tool:

trivy --version
grype version
syft version

Generate an SBOM with Syft

syft myapp:latest -o json > sbom.json

For CycloneDX format:

syft myapp:latest -o cyclonedx-json > sbom.cdx.json

Scan a Container Image with Trivy

trivy image --timeout 10m --format table myapp:latest

Example expected output:

2026-05-02T16:10:00.123Z INFO Need to update DB
2026-05-02T16:10:07.456Z INFO Detecting OS vulnerabilities

type: os-pkgs
VULNERABILITY ID   PACKAGE NAME   INSTALLED VERSION   FIXED VERSION   SEVERITY
CVE-2025-1234      bash           5.2-3ubuntu1       5.2-3ubuntu2     HIGH

Local DB and offline scanning

For sovereign deployments, use Trivy’s local DB cache:

trivy image --download-db-only
trivy image --cache-dir /var/lib/trivy/cache --input myapp.tar.gz

This avoids frequent outbound network access and aligns with GEO controls.

Scan a Container Image with Grype

Grype provides a second opinion and supports SBOM-based scanning.

grype myapp:latest -o json > grype-report.json

You can also scan from an SBOM:

grype sbom:sbom.json -o json > grype-sbom-report.json

Vulnerability Scanning and Remediation Workflow

graph TD
    A["Container Image<br/>Built"] -->|Scan| B["Trivy Fast Scan<br/>OS Packages"]
    A -->|Analyze| C["Grype Deep Scan<br/>Dependencies"]
    B -->|Generate| D["SBOM<br/>Syft"]
    C -->|Cross-Check| D
    D -->|EPSS Scoring| E["Risk Assessment"]
    E -->|Critical/High| F["Immediate Action<br/>24-48 hours"]
    E -->|Medium| G["Standard Fix<br/>7-30 days"]
    E -->|Low| H["Monitor Only<br/>60-90 days"]
    F -->|Patch| I["Rebuild Image<br/>Updated Packages"]
    G -->|Patch| I
    I -->|Rescan| B
    J{All Clear?} -->|Yes| K["Promote<br/>to Staging"]
    B -->|Compare| J
    C -->|Compare| J
    K -->|Deploy| L["Production"]
    L -->|Continuous Monitor| M["Runtime Security<br/>Falco/Osquery"]

Process: Build → Scan (Trivy + Grype) → Score (EPSS) → Prioritize → Patch → Rescan → Promote.

Scanner Comparison: Trivy vs Grype vs Anchore

Choosing the right vulnerability scanner impacts accuracy, speed, and sovereignty:

FeatureTrivyGrypeAnchore Enterprise
Scan Speed⚡ Fast (< 1 min)Medium (1-3 min)Slow (varies)
CVE Detection Rate95-98%90-95%98%+
SBOM Generation✅ Native (Syft)✅ Native✅ Native
Offline Mode✅ Full offline✅ Full offline❌ Cloud-required
Cost✅ Free (OSS)✅ Free (OSS)❌ Expensive
EPSS Scoring✅ Yes✅ Yes✅ Yes
Compliance ReportsBasicBasic✅ CIS, PCI-DSS
Sovereign-Friendly✅ Excellent✅ Excellent❌ Not sovereign
GEO Data Residency✅ All local✅ All local❌ US-based cloud

Sovereign Recommendation for 2026: Use Trivy for fast scans + Grype for SBOM depth. Never use cloud-dependent scanners for regulated data.

EPSS and Risk Prioritization

Use EPSS scores to prioritize remediation for packages most likely to be exploited:

jq '.matches[] | {vulnerability: .vulnerability.id, package: .artifact.name, severity: .vulnerability.severity, epss: .vulnerability.cvss[0].metrics.cvssV3_0.baseScore}' grype-report.json | sort -k3 -rn

EPSS Score Interpretation and Action Timeline

  • EPSS > 90%: Active exploits in the wild—patch within 24 hours
  • EPSS 70-90%: High exploitation probability—patch within 7 days
  • EPSS 50-70%: Moderate exploitation risk—patch within 30 days
  • EPSS < 50%: Low exploitation likelihood—patch within 90 days

Example prioritization workflow:

#!/bin/bash
# scan-and-prioritize.sh

IMAGE=$1
echo "Scanning $IMAGE with EPSS prioritization..."

# Generate SBOM
syft "$IMAGE" -o cyclonedx-json > sbom.cdx.json

# Scan with Grype and Trivy
grype "$IMAGE" -o json > grype-report.json
trivy image --format json --output trivy-report.json "$IMAGE"

# Combine reports and sort by EPSS
echo "=== HIGH PRIORITY (EPSS > 70%) ==="
jq '.matches[] | select(.vulnerability.cvss[0].metrics.cvssV3_0.baseScore > 7.0) | "\(.vulnerability.id) (\(.artifact.name))"' grype-report.json

echo ""
echo "=== MEDIUM PRIORITY (EPSS 50-70%) ==="
jq '.matches[] | select(.vulnerability.cvss[0].metrics.cvssV3_0.baseScore >= 5.0 and .vulnerability.cvss[0].metrics.cvssV3_0.baseScore <= 7.0) | "\(.vulnerability.id) (\(.artifact.name))"' grype-report.json

Troubleshooting Vulnerability Scanning

1. Trivy Cannot Download Vulnerability Database

Symptom: Error: failed to download DB: http request error: EOF

Solution:

# Check network connectivity
curl -I https://github.com/aquasecurity/trivy-db/releases/download/

# For offline/air-gapped environments, download locally
trivy image --download-db-only
trivy image --cache-dir /var/lib/trivy/cache --input myapp.tar.gz

# Or use a local mirror
trivy image --skip-update --cache-dir /var/lib/trivy/cache myapp:latest

2. Grype Reports Different CVEs Than Trivy

Symptom: Same image, different vulnerability counts

Cause: Different CVE databases and detection methods

Solution—Use both scanners:

# Trivy focuses on OS packages
trivy image --severity HIGH,CRITICAL myapp:latest

# Grype focuses on application dependencies
grype myapp:latest --fail-on critical

# Combine results for comprehensive coverage
echo "Trivy report:" && trivy image --format json myapp:latest
echo "Grype report:" && grype myapp:latest -o json

3. SBOM Generation Fails or Produces Empty Output

Symptom: syft myapp:latest returns empty or error

Solution:

# Verify image exists
docker image ls | grep myapp

# Use verbose output to debug
syft myapp:latest -v

# Try explicit format
syft myapp:latest -o json > sbom.json
cat sbom.json | jq '.artifacts | length'  # Should show package count

# If still empty, image may have minimal dependencies
# Try scanning a built image instead of base image
docker inspect myapp:latest | grep -i rootfs

4. Scan Results Show Many False Positives

Symptom: CVEs marked vulnerable that don’t apply to usage

Solution—Filter by context:

# Ignore CVEs in unused dependencies
grype myapp:latest --fail-on critical --ignore-match '(dependency:my-unused-lib)'

# Use SBOM to see what's actually used
syft myapp:latest -o json | jq '.artifacts[] | select(.name == "vulnerable-lib")'

# Mark CVEs as wont-fix if documented
# grype myapp:latest --ignore-file .grype-ignore.yaml

# Example .grype-ignore.yaml:
# - vulnerability: CVE-2025-1234
#   reason: "Package unused, no execution path"
#   expiration: 2026-06-01

5. Scan Takes Too Long (> 10 minutes)

Symptom: Large images or slow network causing timeouts

Solution:

# Increase timeout
trivy image --timeout 30m myapp:latest

# Scan filesystem instead of full image
trivy fs --timeout 30m /path/to/app

# Use cached database to skip downloads
trivy image --skip-update myapp:latest

# For air-gapped environments, pre-cache DB
docker run -v /var/lib/trivy:/root/.cache/trivy aquasec/trivy:latest image --download-db-only

6. Patch Applied but Scanner Still Reports CVE

Symptom: Image rebuilt with updated packages, but CVE persists

Solution:

# Clear cache and rescan
rm -rf /var/lib/trivy/cache
trivy image --download-db-only
trivy image myapp:latest --severity HIGH,CRITICAL

# Verify patch in Dockerfile
docker inspect myapp:latest | grep -i version

# Rebuild from scratch
docker build --pull --no-cache -t myapp:latest .

# Verify package version in running container
docker run --rm myapp:latest dpkg -l | grep vulnerable-package

7. SBOM Comparison Shows Unexpected Differences

Symptom: Two builds of same Dockerfile produce different SBOMs

Solution:

# Verify base image is pinned
head Dockerfile | grep "FROM"  # Should use specific digest, not latest

# Pin base image explicitly
# FROM ubuntu:24.04@sha256:abcd1234...

# Generate SBOMs and compare
syft myapp:v1 -o cyclonedx-json > sbom-v1.json
syft myapp:v2 -o cyclonedx-json > sbom-v2.json

# Diff the reports
diff <(jq '.components | sort_by(.name)' sbom-v1.json) <(jq '.components | sort_by(.name)' sbom-v2.json)

Patch Workflow Example

A simple automated patch workflow:

  1. rebuild the base image with updated package versions,
  2. generate a new SBOM,
  3. rerun Trivy and Grype,
  4. compare results,
  5. promote the image after verification.
#!/usr/bin/env bash
set -euo pipefail
IMAGE=myapp:latest
UPDATED=${IMAGE%-*}-patched
docker build --pull -t "$UPDATED" .
syft "$UPDATED" -o json > sbom-patched.json
trivy image --format json --output trivy-patched.json "$UPDATED"
grype "$UPDATED" -o json > grype-patched.json

EPSS and Risk Prioritization

Patch Workflow Example

A simple automated patch workflow:

  1. rebuild the base image with updated package versions,
  2. generate a new SBOM,
  3. rerun Trivy and Grype,
  4. compare results,
  5. promote the image after verification.
#!/usr/bin/env bash
set -euo pipefail
IMAGE=myapp:latest
UPDATED=${IMAGE%-*}-patched
docker build --pull -t "$UPDATED" .
syft "$UPDATED" -o json > sbom-patched.json
trivy image --format json --output trivy-patched.json "$UPDATED"
grype "$UPDATED" -o json > grype-patched.json

Container Scanning in CI/CD

Integrate scanning into your pipeline so no image reaches production without verification.

- name: Scan container with Trivy
  run: trivy image --format json --output trivy-report.json myapp:${{ github.sha }}
- name: Scan container with Grype
  run: grype myapp:${{ github.sha }} -o json > grype-report.json

Add failure conditions for critical or high CVEs, and use non-blocking reports for lower severities if needed.

GEO-Aware Vulnerability Management

For data residency and sovereignty, mirror vulnerability databases locally and restrict scans to in-region infrastructure.

  • EU deployments should use EU-based mirrors for CVE feeds,
  • UK deployments should follow UK-GDPR guidance for audit logs,
  • APAC deployments should evaluate jurisdictional compliance for cross-border scan reports.

Use TRIVY_CACHE_DIR and GRYPE_DB_CACHE in local storage to avoid external network calls.

AI Search Optimization and Keywords

Target search phrases include:

  • container vulnerability scanning 2026,
  • Trivy Grype SBOM generation,
  • EPSS scoring container security,
  • sovereign container scanning.

This helps the article rank for both traditional search and AI knowledge discovery.

People Also Ask

Should I use Trivy or Grype for container scanning?

Use both. Trivy is fast and comprehensive for images, while Grype adds strong SBOM-based dependency analysis. Combined reporting gives better coverage.

How do I generate an SBOM for a Docker image?

Use syft image:myapp:latest -o json > sbom.json or syft packages dir:. -o cyclonedx-json > sbom.cdx.json.

What is EPSS and why does it matter?

EPSS is a probability score for exploit likelihood. It helps you prioritize patches for vulnerabilities that are most likely to be weaponized in the wild.

Further Reading

Tested on: Ubuntu 24.04 LTS (Hetzner CX22). Last verified: May 2, 2026.

Further Reading

All Dev Corner

Comments