Trivy Container Vulnerability Scanning

Trivy is a comprehensive, open-source vulnerability scanner for container images, filesystems, and code repositories that detects CVEs, misconfigurations, and exposed secrets in CI/CD pipelines. This guide covers installing Trivy on Linux, scanning container images, generating SBOMs, integrating with CI/CD, and enforcing security policies.

Prerequisites

  • Linux server (Ubuntu 22.04/Debian 12 or CentOS/Rocky 9)
  • Docker installed (for container image scanning)
  • Internet access for vulnerability database updates
  • 1 GB+ free disk space for the vulnerability database

Install Trivy

# Ubuntu/Debian
sudo apt install -y wget apt-transport-https gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" \
  | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install -y trivy

# CentOS/Rocky
sudo tee /etc/yum.repos.d/trivy.repo << 'EOF'
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key
EOF
sudo dnf install -y trivy

# Verify installation
trivy --version

# Update the vulnerability database
trivy image --download-db-only

Scan Container Images

Scan a Docker image from Docker Hub or a local image:

# Scan a public image
trivy image nginx:1.25-alpine

# Scan a specific image tag
trivy image python:3.12-slim

# Scan a local Docker image (built but not pushed)
docker build -t myapp:latest .
trivy image myapp:latest

# Scan an image from a private registry
docker login registry.example.com
trivy image registry.example.com/myapp:v1.0

# Scan without pulling (using already-pulled image)
trivy image --input /path/to/image.tar   # exported with docker save

Example output:

myapp:latest (alpine 3.19.0)
=================================
Total: 5 (HIGH: 3, MEDIUM: 2)

┌─────────────────────┬────────────────┬──────────┬───────────────────┬────────────────┐
│       Library       │ Vulnerability  │ Severity │ Installed Version │ Fixed Version  │
├─────────────────────┼────────────────┼──────────┼───────────────────┼────────────────┤
│ openssl             │ CVE-2024-0727  │ HIGH     │ 3.1.4-r2          │ 3.1.4-r3       │
│ libcrypto3          │ CVE-2024-0727  │ HIGH     │ 3.1.4-r2          │ 3.1.4-r3       │
└─────────────────────┴────────────────┴──────────┴───────────────────┴────────────────┘

Scan Filesystems and Repositories

# Scan the current directory (source code, Dockerfiles, configs)
trivy fs .

# Scan a specific path
trivy fs /var/www/html

# Scan a Git repository
trivy repo https://github.com/org/repo

# Scan a local git repo
trivy repo /path/to/local/repo

# Scan only configuration files for misconfigurations
trivy config .

# Scan for secrets (API keys, passwords, etc.)
trivy fs --scanners secret .

# Combined scan: vulns + misconfigs + secrets
trivy fs --scanners vuln,config,secret /app

Severity Filtering and Output Formats

# Only show HIGH and CRITICAL vulnerabilities
trivy image --severity HIGH,CRITICAL nginx:latest

# Ignore unfixed vulnerabilities (no patch available)
trivy image --ignore-unfixed nginx:latest

# Exit with code 1 if any CRITICAL or HIGH vulnerabilities found
# (useful for CI/CD - fails the build)
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest

# JSON output for machine processing
trivy image --format json --output results.json nginx:latest

# Table output (default)
trivy image --format table nginx:latest

# Template-based output
trivy image --format template --template '@html.tpl' --output report.html nginx:latest

# SARIF output (for GitHub Advanced Security)
trivy image --format sarif --output trivy-results.sarif myapp:latest

# JUnit XML for CI/CD reporting
trivy image --format template \
  --template '@/usr/share/trivy/templates/junit.tpl' \
  --output junit-results.xml myapp:latest

SBOM Generation

Generate a Software Bill of Materials (SBOM) documenting all components in a container:

# Generate SBOM in CycloneDX format
trivy image --format cyclonedx --output sbom-cyclonedx.json myapp:latest

# Generate SBOM in SPDX format
trivy image --format spdx-json --output sbom-spdx.json myapp:latest

# Generate SBOM and then scan the SBOM for vulnerabilities
trivy image --format cyclonedx --output sbom.json myapp:latest
trivy sbom sbom.json

# Generate SBOM from a filesystem
trivy fs --format cyclonedx --output sbom-fs.json /app

# Attest SBOM to an image using cosign (supply chain security)
trivy image --format cosign-vuln --output vuln.json myapp:latest
cosign attest --predicate vuln.json --type vuln myapp:latest

CI/CD Integration

GitHub Actions:

# .github/workflows/trivy-scan.yml
name: Container Security Scan

on:
  push:
    branches: [main]
  pull_request:

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Run Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: HIGH,CRITICAL
          exit-code: 1
          ignore-unfixed: true

      - name: Upload results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

GitLab CI:

# .gitlab-ci.yml
container-scanning:
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  stage: test
  script:
    - trivy image
        --exit-code 1
        --severity HIGH,CRITICAL
        --ignore-unfixed
        --format json
        --output trivy-report.json
        ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
  artifacts:
    reports:
      container_scanning: trivy-report.json
  allow_failure: false

Jenkins Pipeline:

// Jenkinsfile
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'docker build -t myapp:${BUILD_NUMBER} .'
            }
        }
        stage('Security Scan') {
            steps {
                sh '''
                    trivy image \
                      --exit-code 0 \
                      --severity HIGH,CRITICAL \
                      --format json \
                      --output trivy-report.json \
                      myapp:${BUILD_NUMBER}
                '''
                // Fail on CRITICAL only
                sh 'trivy image --exit-code 1 --severity CRITICAL myapp:${BUILD_NUMBER}'
            }
            post {
                always {
                    archiveArtifacts artifacts: 'trivy-report.json'
                }
            }
        }
    }
}

Policy Enforcement with OPA

Use Trivy with OPA (Open Policy Agent) to enforce custom policies:

# Create a Rego policy file
cat > /tmp/trivy-policy.rego << 'EOF'
package trivy

# Deny if any CRITICAL vulnerabilities exist
deny[msg] {
    input.Results[_].Vulnerabilities[_].Severity == "CRITICAL"
    msg := sprintf("CRITICAL vulnerability found: %v", [input.Results[_].Vulnerabilities[_].VulnerabilityID])
}

# Deny if image runs as root
deny[msg] {
    input.Metadata.ImageConfig.config.User == ""
    msg := "Container runs as root - specify a non-root USER in Dockerfile"
}
EOF

# Scan with custom policy
trivy image --policy /tmp/trivy-policy.rego myapp:latest

# Scan Kubernetes manifests against policies
trivy config --policy /tmp/k8s-policy.rego /path/to/k8s-manifests/

Troubleshooting

Vulnerability database update fails:

# Update manually
trivy image --download-db-only

# Use an offline database (for air-gapped environments)
# Download on connected system:
trivy image --download-db-only
# Copy ~/.cache/trivy to the air-gapped system
trivy image --skip-db-update --offline-scan myapp:latest

# Check database location
trivy info

High false positive rate:

# Create a .trivyignore file to suppress known false positives
cat > .trivyignore << 'EOF'
# Ignore a specific CVE
CVE-2022-12345

# Ignore a specific CVE in a specific package
CVE-2023-67890 libssl
EOF

trivy image --ignorefile .trivyignore myapp:latest

Scan taking too long:

# Limit to specific vulnerability types
trivy image --scanners vuln myapp:latest   # skip config and secret scanning

# Use --light mode (faster, less data)
trivy image --severity HIGH,CRITICAL myapp:latest

# Cache the database locally
trivy image --cache-dir /var/cache/trivy myapp:latest

Conclusion

Trivy provides fast, accurate vulnerability scanning for container images, filesystems, and source code with minimal configuration. Integrating Trivy into CI/CD pipelines with --exit-code 1 and --severity HIGH,CRITICAL ensures vulnerable images never reach production. SBOM generation provides a complete inventory of software components for compliance audits and supply chain security, and the SARIF output format integrates directly with GitHub's security alerting dashboard.