Trivy container security scanning has become the de-facto standard for detecting vulnerabilities in Docker images before they reach production. If you have ever wondered how attackers exploit known CVEs in base images like ubuntu:20.04 or node:18, or whether your Dockerfile exposes secrets, Trivy gives you the answer in seconds. This guide covers installation, scanning strategies, CI/CD integration, and how to interpret and act on Trivy findings — without drowning in false positives.

Prerequisites

  • Docker installed and running (Docker 20.10+)
  • Linux, macOS, or Windows (WSL2) workstation
  • Basic familiarity with Docker images and Dockerfiles
  • A CI/CD platform (GitHub Actions, GitLab CI, or Jenkins) for pipeline integration
  • Root or sudo access for system-wide Trivy installation (or use the binary install)

Installing Trivy

Trivy ships as a single static binary with no external dependencies. The quickest path on Linux:

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
trivy --version

On Ubuntu/Debian you can add the Aqua Security apt repository for managed updates:

sudo apt-get install wget apt-transport-https gnupg lsb-release -y
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy -y

On macOS with Homebrew: brew install trivy

Trivy downloads its vulnerability database on first run. Force a database update at any time with trivy image --download-db-only.

Scanning Docker Images for Vulnerabilities

The core command is straightforward:

trivy image nginx:latest

Trivy pulls the image if not cached locally, unpacks the layers, and checks every installed OS package and language-level dependency against its vulnerability database (NVD, GitHub Advisory, Alpine secdb, and others). Output shows CVE IDs, severity, installed version, and the fixed version when available.

Filtering by Severity

By default Trivy shows all severities from UNKNOWN to CRITICAL. In practice you want to focus on actionable findings:

trivy image --severity HIGH,CRITICAL nginx:latest

Ignoring Unfixed Vulnerabilities

Many CVEs have no available fix yet. Use --ignore-unfixed to skip them and focus on what you can actually remediate:

trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:latest

Output Formats

Trivy supports multiple output formats for different consumers:

# Human-readable table (default)
trivy image nginx:latest

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

# SARIF for GitHub Security tab integration
trivy image --format sarif --output results.sarif nginx:latest

# CycloneDX SBOM
trivy image --format cyclonedx --output sbom.json nginx:latest

The SARIF format integrates directly with GitHub’s Security tab, showing vulnerability annotations inline on pull requests.

Scanning Dockerfiles and IaC Misconfigurations

Trivy’s config scanner checks Dockerfiles, Kubernetes manifests, Terraform, and Helm charts for security misconfigurations before they are deployed:

# Scan a Dockerfile
trivy config ./Dockerfile

# Scan an entire directory (Kubernetes manifests, Terraform)
trivy config ./k8s/

# Scan Helm charts
trivy config --helm-values values.yaml ./charts/myapp

Common Dockerfile findings include: running as root, using ADD instead of COPY, missing HEALTHCHECK, exposing sensitive ports, and not pinning base image digests.

Detecting Secrets and Sensitive Data

Trivy can scan image layers and filesystems for accidentally baked-in secrets:

# Enable secret scanning on an image
trivy image --scanners secret nginx:latest

# Scan a local directory for secrets
trivy fs --scanners secret ./src/

Trivy detects AWS keys, GitHub tokens, private SSH keys, database connection strings, and hundreds of other secret patterns. This is particularly valuable for catching secrets committed to source code that ended up in a Docker layer.

Comparing Trivy Against Alternatives

FeatureTrivyGrypeSnykClair
OS package CVEsYesYesYesYes
Language depsYesYesYesNo
IaC misconfigsYesNoYesNo
Secret scanningYesNoYesNo
SBOM generationYesYesYesNo
LicenseApache 2.0Apache 2.0ProprietaryApache 2.0
CI/CD integrationNativeNativeNativeLimited
Offline modeYesYesNoPartial

Trivy’s advantage is breadth — it combines vulnerability scanning, misconfiguration detection, secret scanning, and SBOM generation in a single binary without requiring a running server or database.

Integrating Trivy into CI/CD Pipelines

GitHub Actions

name: Container Security Scan

on:
  push:
    branches: [main]
  pull_request:

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

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

      - name: Run Trivy vulnerability scanner
        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 Trivy scan results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

The exit-code: 1 setting fails the pipeline when critical or high CVEs are found. if: always() on the SARIF upload ensures results are visible even when the scan step fails.

GitLab CI

trivy-scan:
  image: aquasec/trivy:latest
  stage: test
  script:
    - trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  allow_failure: false

Real-World Scenario

You have a production application running node:16 as its base image. Your security team flags that the CI/CD pipeline never validates container images before deployment. You have 48 hours to implement a scanning gate.

Step 1 — Audit the current image:

trivy image node:16 --severity HIGH,CRITICAL --ignore-unfixed

This reveals 47 HIGH and 12 CRITICAL CVEs in the base image — most fixed by upgrading to node:18-alpine.

Step 2 — Update the Dockerfile:

# Before
FROM node:16

# After
FROM node:18-alpine

Step 3 — Re-scan to confirm remediation:

trivy image node:18-alpine --severity HIGH,CRITICAL --ignore-unfixed

The alpine variant drops the CVE count from 59 to 3 (all unfixed).

Step 4 — Add the CI gate:

Add the GitHub Actions workflow above. All future PRs are blocked if CRITICAL CVEs are introduced.

Step 5 — Scan IaC too:

trivy config ./k8s/ --severity HIGH,CRITICAL

This surfaces two issues: a deployment running as root and a missing readOnlyRootFilesystem security context setting.

Gotchas and Edge Cases

Vulnerability database staleness: Trivy caches its database locally. In CI environments run trivy image --download-db-only as a separate cached step to avoid redundant downloads.

Private registry authentication: Export credentials as environment variables before scanning:

export TRIVY_USERNAME=myuser
export TRIVY_PASSWORD=mypassword
trivy image myregistry.example.com/myapp:latest

False positives: Some CVEs are flagged in libraries that your application never calls. Use .trivyignore to suppress known false positives:

# .trivyignore
CVE-2022-12345  # Not exploitable — library not called at runtime

Alpine musl vs glibc: Alpine-based images report fewer CVEs because Alpine uses musl libc and the Alpine security team patches aggressively. This is not “missing” coverage — it reflects genuine differences in the library ecosystem.

Multi-arch images: Always specify the platform when scanning multi-arch images to get the right results: trivy image --platform linux/amd64 myapp:latest.

Trivy vs runtime security: Trivy finds vulnerabilities at build time. It does not replace runtime security tools like Falco that detect unexpected behavior in running containers.

Troubleshooting

“No such image” error: Trivy cannot find the image locally and cannot pull it. Confirm the image name, tag, and registry credentials.

Database download failures in air-gapped environments: Download the database bundle on an internet-connected machine with trivy image --download-db-only, then transfer the ~/.cache/trivy/ directory to the air-gapped host.

High memory usage on large images: Use --parallel 1 to reduce concurrent layer processing: trivy image --parallel 1 myapp:latest.

Exit code 1 but no critical CVEs shown: Check whether --ignore-unfixed is omitted — unfixed critical CVEs can trigger the exit code even if you expected to see only fixable issues.

Scan timeout in CI: Set TRIVY_TIMEOUT=10m to extend the default 5-minute timeout for large images.

Summary

  • Trivy is a single-binary scanner covering CVEs, misconfigurations, secrets, and SBOM generation
  • Run trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:latest as your baseline scan
  • Use --exit-code 1 in CI/CD to block deployments with critical vulnerabilities
  • Combine trivy image (runtime) with trivy config (Dockerfile/IaC) for full coverage
  • Alpine-based images dramatically reduce CVE surface area
  • Use .trivyignore to suppress confirmed false positives and reduce alert fatigue
  • Upload SARIF results to GitHub Security tab for inline PR annotations
  • Trivy does not replace runtime security — pair it with Falco for defense in depth