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:
| Feature | Trivy | Grype | Anchore Enterprise |
|---|---|---|---|
| Scan Speed | ⚡ Fast (< 1 min) | Medium (1-3 min) | Slow (varies) |
| CVE Detection Rate | 95-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 Reports | Basic | Basic | ✅ 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:
- rebuild the base image with updated package versions,
- generate a new SBOM,
- rerun Trivy and Grype,
- compare results,
- 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:
- rebuild the base image with updated package versions,
- generate a new SBOM,
- rerun Trivy and Grype,
- compare results,
- 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
- CI/CD Pipeline Design Guide 2026: Build, Test, Scan & Deploy Securely
- AI Agent Security 2026: Prompt Injection, Tool Permissions & Sandboxing
- Caddy Reverse Proxy Tutorial 2026: Automatic HTTPS for Docker Apps
Tested on: Ubuntu 24.04 LTS (Hetzner CX22). Last verified: May 2, 2026.