Back to Distributed Systems & Kubernetes Series

Trivy Track Part 1: Image Scanning & CI Integration

June 6, 2026 Wasil Zafar 32 min read

Trivy is a comprehensive, fast, and easy-to-use security scanner. It finds vulnerabilities, misconfigurations, secrets, and SBOMs in container images, file systems, and IaC configs — all without requiring a separate database server. Integrate it into your CI/CD pipelines to catch security issues before they reach production.

Table of Contents

  1. What is Trivy
  2. Image Scanning
  3. CI Pipeline Integration
  4. Filesystem & Config Scanning
  5. Custom Policies
  6. Exercises
  7. Key Takeaways & Next Steps

What is Trivy

Trivy is an open-source security scanner by Aqua Security. Unlike traditional vulnerability scanners that require a server-side database and complex setup, Trivy operates as a single binary with an embedded vulnerability database that auto-updates.

  • Vulnerability Scanning — Detects known CVEs in OS packages (Alpine, Debian, RHEL) and language-specific packages (npm, pip, Go modules, Maven)
  • Misconfiguration Detection — Checks Dockerfiles, Kubernetes manifests, Terraform, and CloudFormation for security issues
  • Secret Detection — Finds hardcoded API keys, passwords, tokens, and certificates in code and container layers
  • SBOM Generation — Creates Software Bill of Materials in CycloneDX or SPDX format
  • License Scanning — Identifies package licenses for compliance checks
Why Trivy: Zero setup, no database server, single binary, supports multiple targets (images, filesystems, repos, K8s clusters), and integrates natively with CI/CD. It downloads its vulnerability DB on first run (~30MB) and caches it locally.
# Install Trivy on Linux/macOS
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.52.0

# Verify installation
trivy --version
# Version: 0.52.0

Image Scanning

Image scanning is Trivy's most common use case. It pulls the image (or uses a local copy), extracts layers, identifies packages, and checks each against the vulnerability database.

Basic Scan

# Scan a public image from Docker Hub
trivy image nginx:latest

# Scan a specific version
trivy image python:3.11-slim

# Scan a private registry image (uses Docker credentials)
trivy image ghcr.io/myorg/myapp:v1.2.3

# Scan a locally built image (no pull required)
docker build -t myapp:dev .
trivy image myapp:dev

The default output is a table showing vulnerability ID (CVE), package name, installed version, fixed version, and severity level.

Severity Filtering

# Show only HIGH and CRITICAL vulnerabilities
trivy image --severity HIGH,CRITICAL nginx:latest

# Show only CRITICAL (useful for CI gate decisions)
trivy image --severity CRITICAL alpine:3.18

# Ignore unfixed vulnerabilities (only show fixable ones)
trivy image --ignore-unfixed nginx:latest

# Combine: only fixable HIGH/CRITICAL
trivy image --severity HIGH,CRITICAL --ignore-unfixed python:3.11-slim

Output Formats

# Default: table format (human-readable)
trivy image --format table nginx:latest

# JSON output (for programmatic consumption)
trivy image --format json --output results.json nginx:latest

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

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

# Template-based output (custom formatting)
trivy image --format template \
  --template "@contrib/html.tpl" \
  --output report.html nginx:latest
SARIF Integration: Upload SARIF files to GitHub's code scanning to see Trivy findings directly in the Security tab of your repository. This enables tracking, dismissing, and alerting on vulnerabilities alongside your code.

CI Pipeline Integration

Trivy's true power emerges in CI/CD. By scanning every image before it's pushed to a registry or deployed, you create a security gate that blocks vulnerable images from reaching production.

GitHub Actions

# .github/workflows/security-scan.yaml
name: Container Security Scan
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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: table
          exit-code: 1
          severity: HIGH,CRITICAL
          ignore-unfixed: true

      - name: Upload SARIF to GitHub Security
        if: always()
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif

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

Exit Codes for CI Gates

# exit-code 0: always succeed (report only, don't block)
trivy image --exit-code 0 --severity HIGH,CRITICAL myapp:latest

# exit-code 1: fail the pipeline if vulnerabilities found
trivy image --exit-code 1 --severity CRITICAL myapp:latest
echo $?  # 1 if CRITICAL CVEs found, 0 otherwise

# Common CI pattern: warn on HIGH, block on CRITICAL
trivy image --exit-code 0 --severity HIGH myapp:latest
trivy image --exit-code 1 --severity CRITICAL myapp:latest
CI Best Practice: Use --exit-code 1 --severity CRITICAL as a hard gate and --exit-code 0 --severity HIGH as an informational check. This blocks truly dangerous vulnerabilities while giving teams visibility into high-severity issues without disrupting deployments.

Filesystem & Config Scanning

Beyond images, Trivy scans source code directories for vulnerabilities in dependency lock files, and Infrastructure-as-Code files for misconfigurations.

# Scan the current directory for vulnerable dependencies
# (checks package-lock.json, requirements.txt, go.sum, pom.xml, etc.)
trivy fs .

# Scan only for misconfigurations in Kubernetes manifests
trivy config k8s/

# Scan a Dockerfile for misconfigurations
trivy config Dockerfile

# Scan Terraform files
trivy config --tf-vars terraform.tfvars terraform/

# Combined: vulnerabilities + misconfigs + secrets
trivy fs --scanners vuln,misconfig,secret .

Example Dockerfile misconfiguration findings:

# Trivy detects common Dockerfile issues:
# - Running as root (no USER instruction)
# - Using latest tag (non-reproducible builds)
# - ADD instead of COPY (unexpected behavior with URLs/archives)
# - Secrets in ENV or ARG instructions

trivy config Dockerfile
# OUTPUT:
# Dockerfile (dockerfile)
# ========================
# Tests: 23 (SUCCESSES: 20, FAILURES: 3)
# Failures: 3 (HIGH: 2, MEDIUM: 1)
#
# HIGH: Specify a tag in the 'FROM' statement
# ─────────────────────────────────────
# Dockerfile:1 - FROM python
#
# HIGH: Do not use 'root' user
# ─────────────────────────────────────
# No USER instruction found

Custom Policies

Trivy supports .trivyignore files to suppress known false positives and a trivy.yaml configuration file for persistent settings.

# .trivyignore — suppress specific CVEs
# Format: CVE-ID followed by optional comment

# Known false positive in our base image, no actual exposure
CVE-2023-44487

# Accepted risk: low-priority vuln in dev dependency only
CVE-2024-12345

# Temporarily ignored while waiting for upstream fix (expires 2026-07-01)
CVE-2026-98765  # TODO: remove after base image update
# trivy.yaml — persistent configuration
# Place in project root or ~/.trivy.yaml

scan:
  # Security checks to perform
  scanners:
    - vuln
    - misconfig
    - secret

severity:
  - HIGH
  - CRITICAL

# Ignore unfixed vulnerabilities
ignore-unfixed: true

# Cache directory
cache:
  dir: /tmp/trivy-cache

# Vulnerability database
db:
  skip-update: false

# Output
format: table

# Image-specific settings
image:
  # Remove credentials after scan
  remove-pkgs: false

Exercises

Exercise 1: Install Trivy locally. Scan three different base images (nginx:latest, python:3.11-slim, alpine:3.18) with --severity HIGH,CRITICAL. Compare the number of vulnerabilities found in each. Which base image has the smallest attack surface?
Exercise 2: Create a GitHub Actions workflow that builds a Docker image and scans it with Trivy. Configure it to: (a) fail the pipeline on CRITICAL vulnerabilities, (b) upload SARIF results to the GitHub Security tab, and (c) produce a JSON report as a build artifact. Push a deliberately vulnerable Dockerfile (e.g., using FROM python:3.8) and verify the pipeline fails.
Exercise 3: Create a .trivyignore file to suppress a specific CVE from Exercise 1. Then create a trivy.yaml config file that sets default severity to HIGH,CRITICAL and enables misconfiguration scanning. Run trivy config on a Kubernetes deployment manifest and fix any misconfigurations Trivy reports.

Key Takeaways & Next Steps

  • Trivy is a single-binary, zero-config scanner for vulnerabilities, misconfigurations, secrets, and SBOMs
  • trivy image scans container images; trivy fs scans source code; trivy config scans IaC
  • Use --severity HIGH,CRITICAL --exit-code 1 in CI to block deployments with serious CVEs
  • SARIF output integrates with GitHub Security for centralized vulnerability tracking
  • .trivyignore suppresses accepted risks; trivy.yaml stores persistent configuration
  • Filesystem scanning catches vulnerable dependencies before they're baked into images

Next in the Series

In Part 2: Operator & Cluster Reports, we'll deploy the Trivy Operator for continuous in-cluster scanning, explore VulnerabilityReport and ConfigAuditReport CRDs, and set up Prometheus/Grafana alerting on vulnerability findings.