LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

Trivy: Container and IaC Vulnerability Scanning

securitydockerkubernetestrivydevopscontainersiacsbom

Every container you ship has a bill of materials: the OS packages, language libraries, and binaries baked into the image. Most of them have vulnerabilities. Your job is to know which ones, triage by severity and fix availability, and not block shipping over a LOW-severity CVE in a library you don’t actually call.

Trivy makes that practical. It’s a single binary that scans container images, filesystems, git repositories, Terraform configs, Kubernetes manifests, running clusters, and generates SBOMs — all without a database server or account signup. Install it, point it at something, get results in seconds.

This guide covers the full workflow: image scanning, IaC misconfiguration detection, secret detection, Kubernetes cluster scanning, the Trivy Operator for continuous cluster monitoring, CI/CD integration, and how to configure ignore rules so your builds don’t fail on noise.


What Trivy Scans

Trivy has six scanner types, all available in the same binary:

Scanner What it finds
vuln CVEs in OS packages and language dependencies
misconfig Security misconfigurations in IaC and Kubernetes
secret Hardcoded credentials, API keys, private keys
license Dependency licenses (GPL, MIT, Apache, etc.)
sbom Generates or consumes Software Bills of Materials

And it targets multiple input types: container images, local filesystems, git repos, Kubernetes clusters, VM images, and SBOMs.

Vulnerability data comes from: OSV (Open Source Vulnerabilities), NVD (National Vulnerability Database), GitHub Advisory Database, vendor-specific OS advisories (Debian, Ubuntu, Alpine, RHEL, Amazon Linux), and language ecosystem databases (PyPI, npm, RubyGems, Maven, Go module proxy, Cargo, NuGet).


Installation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# macOS
brew install trivy

# Debian / Ubuntu
sudo apt-get install wget apt-transport-https gnupg
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 generic main" | \
  sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy

# Via install script (any Linux)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | \
  sudo sh -s -- -b /usr/local/bin

# Docker (no install needed)
docker run --rm aquasec/trivy image nginx:latest

# mise / asdf
mise use -g trivy

Verify:

1
2
trivy --version
trivy image --download-db-only  # Pre-fetch the vuln DB

Container Image Scanning

Basic scan

1
2
3
4
5
6
7
8
9
# Scan a local image (must be pulled or built first)
trivy image nginx:latest

# Scan a remote image directly
trivy image python:3.12-slim

# Scan your own image
docker build -t my-app:dev .
trivy image my-app:dev

Default output shows a table grouped by target (OS packages, then each language ecosystem found):

nginx:latest (debian 12.9)
==========================
Total: 148 (UNKNOWN: 0, LOW: 105, MEDIUM: 29, HIGH: 12, CRITICAL: 2)

┌──────────────┬───────────────┬──────────┬──────────────────┬───────────────┬────────────────────────────────┐
│   Library    │ Vulnerability │ Severity │ Installed Version│ Fixed Version │           Title                │
├──────────────┼───────────────┼──────────┼──────────────────┼───────────────┼────────────────────────────────┤
│ openssl      │ CVE-2024-0727 │ CRITICAL │ 3.0.11-1~deb12u2 │ 3.0.13-1~deb │ OpenSSL: denial of service     │
│ zlib1g       │ CVE-2023-45853│ CRITICAL │ 1:1.2.13.dfsg-1  │               │ zlib: integer overflow         │
└──────────────┴───────────────┴──────────┴──────────────────┴───────────────┴────────────────────────────────┘

Filtering to what matters

In practice, most images have dozens to hundreds of LOW/MEDIUM findings, many without fixes. Filter immediately:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Only HIGH and CRITICAL
trivy image --severity HIGH,CRITICAL nginx:latest

# Only vulnerabilities that have fixes available
trivy image --ignore-unfixed nginx:latest

# Both — the most actionable filter in CI
trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:latest

# Scan only vulnerabilities, skip misconfig/secret scanners
trivy image --scanners vuln nginx:latest

Output formats

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# JSON (for programmatic processing or archiving)
trivy image --format json --output results.json nginx:latest

# SARIF (for GitHub Code Scanning — uploads to Security tab)
trivy image --format sarif --output results.sarif nginx:latest

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

# SPDX SBOM
trivy image --format spdx-json --output sbom.spdx.json nginx:latest

Scanning private registries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# AWS ECR — authenticate via AWS CLI first
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789012.dkr.ecr.us-east-1.amazonaws.com
trivy image 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest

# GCR / Artifact Registry
gcloud auth configure-docker us-central1-docker.pkg.dev
trivy image us-central1-docker.pkg.dev/my-project/my-repo/my-app:latest

# Azure ACR
az acr login --name myregistry
trivy image myregistry.azurecr.io/my-app:latest

# Harbor or any other registry — docker login first
docker login registry.example.com -u user -p pass
trivy image registry.example.com/my-app:v1.2.3

Trivy reads Docker’s credential store after docker login, so authentication flows the same as docker pull.


Filesystem and Repository Scanning

Scan a local directory for vulnerabilities in dependency files (package.json, requirements.txt, go.sum, Gemfile.lock, pom.xml, etc.):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Scan current directory
trivy fs .

# Scan with all scanners enabled
trivy fs --scanners vuln,misconfig,secret,license .

# Scan a specific project
trivy fs /path/to/my-app

# Scan a remote git repo (clones then scans)
trivy repo https://github.com/org/repo
trivy repo https://github.com/org/repo --branch main

Skipping directories and files

1
2
3
# Skip at the CLI
trivy fs --skip-dirs node_modules,vendor,.terraform .
trivy fs --skip-files "**/*.log","**/*.lock" .

Or configure in trivy.yaml (place in project root or ~/.trivy.yaml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
scan:
  skip-dirs:
    - node_modules
    - vendor
    - .terraform
    - "**/testdata/**"
    - dist
    - build
  skip-files:
    - "**/*.log"
    - "**/yarn.lock"    # If you only care about package.json
    - "**/poetry.lock"  # If scanning requirements.txt is enough

IaC and Misconfiguration Scanning

Trivy detects security misconfigurations in Terraform, Helm charts, Kubernetes manifests, and Dockerfiles using the config or fs --scanners misconfig mode.

Terraform

1
2
3
4
5
# Scan a Terraform directory
trivy config ./terraform/

# Scan a specific file
trivy config ./terraform/main.tf

Example findings:

main.tf (terraform)
===================
Tests: 18 (SUCCESSES: 14, FAILURES: 4, EXCEPTIONS: 0)
Failures: 4 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 1, CRITICAL: 1)

CRITICAL: Security group rule allows ingress from 0.0.0.0/0 on port 22
AVD-AWS-0107
─────────────────────────────────────────────────────────────────────
 25 │   cidr_blocks = ["0.0.0.0/0"]  ← line flagged

HIGH: S3 bucket has public ACL set
AVD-AWS-0176
─────────────────────────────────────────────────────────────────────

Kubernetes manifests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Scan a manifest file
trivy config deployment.yaml

# Scan an entire manifests directory
trivy config ./k8s/

# Scan a Helm chart (rendered and checked)
trivy config ./charts/my-app/

# Helm with values
helm template my-release ./charts/my-app -f values-prod.yaml | trivy config -

Common findings in Kubernetes manifests:

  • Container running as root (runAsNonRoot: false or missing)
  • allowPrivilegeEscalation: true
  • Missing readOnlyRootFilesystem
  • Missing CPU/memory limits
  • Using hostNetwork: true or hostPID: true
  • Privileged containers
  • Missing liveness/readiness probes

Dockerfiles

1
2
trivy config Dockerfile
trivy config ./docker/  # Scans all Dockerfiles in directory

Dockerfile findings:

Dockerfile (dockerfile)
=======================
Tests: 23 (SUCCESSES: 18, FAILURES: 5)

HIGH: Last USER command in Dockerfile is 'root'
DKR001
Ensure the last USER directive is not 'root'.

MEDIUM: ADD command used instead of COPY
DKR002

LOW: No HEALTHCHECK instruction in Dockerfile
DKR006

Secret Detection

Trivy scans for hardcoded credentials across all scan types. It’s enabled by default for image and filesystem scans.

1
2
3
4
5
6
7
8
# Scan image for secrets (included in default scan)
trivy image my-app:latest

# Scan a repo specifically for secrets
trivy repo --scanners secret https://github.com/org/repo

# Filesystem secret scan
trivy fs --scanners secret .

Trivy detects:

  • AWS access keys and secret keys
  • GCP service account JSON keys
  • GitHub/GitLab personal access tokens
  • Slack tokens and webhook URLs
  • Private keys (RSA, EC, DSA, OpenSSH)
  • Generic high-entropy strings matching credential patterns
  • Database connection strings with embedded passwords
  • Azure storage account keys

Custom secret rules

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# trivy-secret.yaml
rules:
  - id: custom-internal-api-key
    category: InternalService
    title: Internal API Key Exposed
    severity: HIGH
    regex: 'MYCO_API_KEY["\s]*[:=]\s*["\']?([a-zA-Z0-9]{40})["\']?'
    keywords:
      - MYCO_API_KEY

allow-rules:
  # Whitelist known test fixtures
  - id: aws-access-key-id
    paths:
      - "tests/fixtures/**"
      - "docs/examples/**"
1
trivy image --secret-config ./trivy-secret.yaml my-app:latest

License Scanning

Identify which open-source licenses are in your dependency tree — useful for legal review and catching unexpected copyleft licenses:

1
2
3
4
5
6
7
8
# Scan filesystem dependencies for licenses
trivy fs --scanners license .

# Scan image (OS packages + language deps)
trivy image --scanners license nginx:latest

# Extended scan: also reads source files, LICENSE docs, README headers
trivy image --license-full my-app:latest

Output groups licenses by category:

my-app:latest (license)
=======================
┌──────────────────┬──────────────────┬───────────────┐
│     Package      │    License       │   Category    │
├──────────────────┼──────────────────┼───────────────┤
│ django           │ BSD-3-Clause     │ Permissive    │
│ psycopg2         │ LGPL-3.0         │ Weak Copyleft │
│ some-lib         │ GPL-3.0          │ Restricted    │  ← flagged
└──────────────────┴──────────────────┴───────────────┘

License categories:

  • Permissive — MIT, Apache-2.0, BSD: generally safe for commercial use
  • Weak Copyleft — LGPL, MPL-2.0: linking usually fine, modifications require sharing
  • Restricted — GPL-2.0, GPL-3.0, AGPL-3.0: strong copyleft, may require open-sourcing your code

Ignoring Findings

.trivyignore (simple format)

# Ignore a CVE globally
CVE-2019-14697

# Ignore with expiry (auto-reinstates after date)
CVE-2021-12345 exp:2026-06-01

# Comment for context
CVE-2020-99999 exp:2026-12-31
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
vulnerabilities:
  - id: CVE-2019-14697
    comment: "Alpine musl libc — no upstream fix, low exploitability in our context"

  - id: CVE-2021-12345
    comment: "Only in test fixtures, not reachable in production"
    paths:
      - "**/test/**"
      - "**/testdata/**"
    exp: 2026-09-01

  - id: CVE-2023-99999
    comment: "Accepted risk — vendor advisory states not exploitable on our platform"
    exp: 2026-06-30

misconfigurations:
  - id: AVD-AWS-0107
    comment: "Security group 22/0.0.0.0 is intentional for bastion host"

  - id: DS002
    comment: "Running as root required for this init container"
    paths:
      - "k8s/init-job.yaml"

secrets:
  - id: aws-access-key-id
    comment: "Example credentials in documentation only"
    paths:
      - "docs/**"
      - "README.md"

licenses:
  - id: GPL-3.0
    comment: "Legal review approved — not linked into our binary"
    exp: 2026-12-31
1
2
# Use explicitly (or place .trivyignore.yaml in project root for auto-detection)
trivy image --ignorefile .trivyignore.yaml my-app:latest

Kubernetes Cluster Scanning

Trivy can scan a live cluster — checking running workload images for CVEs and cluster resources for misconfigurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Scan the entire cluster (uses current kubectl context)
trivy k8s cluster

# Scan with all scanners
trivy k8s cluster --scanners vuln,misconfig,secret

# Only scan vulnerabilities in running images
trivy k8s cluster --scanners vuln

# Scan a specific namespace
trivy k8s --include-namespaces production cluster

# Scan a specific resource type
trivy k8s deployment
trivy k8s pod --namespace kube-system

# Output to JSON for processing
trivy k8s cluster --format json --output cluster-results.json

# Compliance check against CIS Kubernetes Benchmark
trivy k8s cluster --compliance k8s-cis

trivy k8s combines image vulnerability scanning (from the registry) with configuration auditing (from the live manifests). It reports:

  • CVEs in images running in your cluster
  • Containers running as root
  • Missing resource limits
  • Privileged pods
  • Exposed secrets in ConfigMaps or environment variables
  • RBAC misconfigurations

SBOM Generation

Generate a Software Bill of Materials for compliance, supply chain security, and vulnerability tracking:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# CycloneDX (most widely supported)
trivy image --format cyclonedx --output sbom.cdx.json nginx:latest
trivy image --format cyclonedx --output sbom.cdx.xml nginx:latest

# SPDX (ISO standard)
trivy image --format spdx --output sbom.spdx nginx:latest
trivy image --format spdx-json --output sbom.spdx.json nginx:latest

# Generate SBOM from a filesystem (your app's dependencies)
trivy fs --format cyclonedx --output sbom.cdx.json .

# Scan an existing SBOM for vulnerabilities
trivy sbom ./sbom.cdx.json

The SBOM workflow:

  1. Generate SBOM at build time and attach to the container image (via cosign attach sbom)
  2. Consumers can scan the SBOM with trivy sbom without pulling the full image
  3. Re-scan SBOMs when new CVEs are disclosed without rebuilding

CI/CD Integration

GitHub Actions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write  # Required for SARIF upload

    steps:
      - uses: actions/checkout@v4

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

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

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

Results appear in the repository’s Security → Code scanning tab, linked to the specific commit that introduced each vulnerability.

Scanning IaC in pull requests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  iac-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Trivy IaC scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: config
          scan-ref: .
          format: sarif
          output: trivy-iac.sarif
          severity: HIGH,CRITICAL
          exit-code: 1

      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-iac.sarif
          category: iac

GitLab CI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
trivy-scan:
  image: aquasec/trivy:latest
  stage: security
  variables:
    TRIVY_EXIT_CODE: 1
    TRIVY_SEVERITY: HIGH,CRITICAL
    TRIVY_IGNORE_UNFIXED: "true"
  script:
    - trivy image
        --format json
        --output trivy-report.json
        ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
  artifacts:
    when: always
    reports:
      # GitLab parses this automatically
      container_scanning: trivy-report.json
    paths:
      - trivy-report.json
    expire_in: 1 week

Exit codes

Exit code Meaning
0 No vulnerabilities found at the specified severity
1 Vulnerabilities found
2 Error (misconfiguration, scan failure)

Use --exit-code 0 to collect results without failing the build (for reporting-only pipelines):

1
2
3
4
5
6
# Report only — never fail CI
trivy image --exit-code 0 --format json --output report.json my-app:latest

# Fail CI on CRITICAL only, report HIGH for visibility
trivy image --severity CRITICAL --exit-code 1 --format json --output critical.json my-app:latest
trivy image --severity HIGH --exit-code 0 --format json --output high.json my-app:latest

Trivy Server Mode

In environments where many machines scan images, running each with its own local DB copy means every machine re-downloads the same ~200 MB vulnerability database. Server mode centralizes the DB:

1
2
3
4
5
6
# Start the server (on a shared host or in Kubernetes)
trivy server --listen 0.0.0.0:4954 --cache-dir /var/trivy-cache

# Clients point at the server instead of a local DB
trivy image --server http://trivy-server:4954 nginx:latest
trivy fs --server http://trivy-server:4954 .

Kubernetes deployment

1
2
3
4
5
6
7
8
9
helm repo add aquasecurity https://aquasecurity.github.io/helm-charts/
helm repo update

helm install trivy-server aquasecurity/trivy-server \
  --namespace trivy-system \
  --create-namespace \
  --set service.type=ClusterIP \
  --set persistence.enabled=true \
  --set persistence.size=2Gi

Clients in the cluster:

1
2
# In CI or scan jobs:
TRIVY_SERVER: http://trivy-server.trivy-system.svc.cluster.local:4954

Trivy Operator: Continuous Kubernetes Scanning

The Trivy Operator watches your cluster and automatically scans every workload, storing results as Kubernetes custom resources. You get always-current vulnerability reports without running scans manually.

Installation

1
2
3
4
5
6
7
helm repo add aquasecurity https://aquasecurity.github.io/helm-charts/
helm repo update

helm install trivy-operator aquasecurity/trivy-operator \
  --namespace trivy-system \
  --create-namespace \
  --set trivy.ignoreUnfixed=true

What gets created automatically

Deploy anything and the operator scans it within minutes:

1
2
3
4
5
6
7
8
kubectl create deployment nginx --image nginx:1.24
kubectl get vulnerabilityreports -n default -o wide
# NAME                                REPOSITORY    TAG    SCANNER  AGE    CRITICAL  HIGH  MEDIUM  LOW
# replicaset-nginx-xxx-nginx          nginx         1.24   Trivy    2m     2         12    29      105

kubectl get configauditreports -n default -o wide
# NAME                            SCANNER  AGE    CRITICAL  HIGH  MEDIUM  LOW
# replicaset-nginx-xxx            Trivy    2m     0         3     8       2

CRDs

VulnerabilityReport — CVEs in container images:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: aquasecurity.github.io/v1alpha1
kind: VulnerabilityReport
metadata:
  name: replicaset-nginx-abc-nginx
  namespace: default
  labels:
    trivy-operator.resource.kind: ReplicaSet
    trivy-operator.resource.name: nginx-abc
spec:
  scanner:
    name: Trivy
    version: "0.50.0"
  artifact:
    repository: nginx
    tag: "1.24"
  summary:
    criticalCount: 2
    highCount: 12
    mediumCount: 29
    lowCount: 105
  vulnerabilities:
    - vulnerabilityID: CVE-2024-0727
      resource: openssl
      installedVersion: "3.0.11"
      fixedVersion: "3.0.13"
      severity: CRITICAL
      title: "OpenSSL: denial of service via null pointer dereference"

ConfigAuditReport — Kubernetes misconfigurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: aquasecurity.github.io/v1alpha1
kind: ConfigAuditReport
metadata:
  name: replicaset-nginx-abc
spec:
  checks:
    - id: KSV001
      title: "Process can elevate its own privileges"
      severity: HIGH
      success: false
      messages:
        - "Container 'nginx' of ReplicaSet 'nginx-abc' should set allowPrivilegeEscalation to false"
    - id: KSV003
      title: "Default capabilities not dropped"
      severity: MEDIUM
      success: false

Prometheus metrics

The operator exposes Prometheus metrics for all reports:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: trivy-operator
  namespace: trivy-system
  labels:
    release: kube-prometheus-stack  # Match your Prometheus selector
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: trivy-operator
  endpoints:
    - port: metrics
      interval: 60s
  namespaceSelector:
    matchNames:
      - trivy-system

Key PromQL queries:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Total critical vulnerabilities across the cluster
sum(trivy_image_vulnerabilities{severity="CRITICAL"})

# Critical vulnerabilities by namespace
sum(trivy_image_vulnerabilities{severity="CRITICAL"}) by (namespace)

# Namespaces with the most critical findings (top 5)
topk(5, sum(trivy_image_vulnerabilities{severity="CRITICAL"}) by (namespace))

# Config audit failures by severity
sum(trivy_resource_configaudits{severity="HIGH"}) by (namespace)

# Track remediation over time (should trend down)
sum(trivy_image_vulnerabilities{severity="CRITICAL",namespace="production"})

Import Grafana dashboard ID 16337 (Trivy Operator Dashboard) for pre-built visualizations.


Comparison with Alternatives

Tool Type Strengths Weaknesses
Trivy All-in-one Multi-target, IaC, secrets, SBOM, K8s operator, no account needed Less precision on some ecosystems vs specialized tools
Grype Vuln-only High accuracy, EPSS+KEV risk scoring, fast No IaC, secret, or license scanning
Snyk Platform Fix PRs, developer IDE integration, SAST Paid for serious use, account required, rate limits on free tier
Clair Registry-native Continuous scanning as images are pushed Complex setup, requires separate service, API-driven
Docker Scout Docker-native Zero setup for Docker Hub users, integrated into docker CLI Weaker IaC/cluster scanning, some features paid

When to use Trivy: You want one tool for images, IaC, cluster scanning, and SBOMs. You don’t want to manage accounts or rate limits. You need air-gap support.

When to complement with Grype: Your team needs more precise risk scoring (EPSS probability + KEV known-exploited weighting) to prioritize which CVEs to fix first.


Practical Workflow

A real shift-left security workflow using Trivy at every stage:

Developer workstation:
  trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:dev
  trivy config ./k8s/ ./terraform/
  trivy fs --scanners secret .

Pull request (GitHub Actions):
  trivy-action → SARIF → GitHub Security tab
  trivy config → SARIF → GitHub Security tab

Container registry (on push to main):
  trivy image --format cyclonedx --output sbom.cdx.json → attach to image
  trivy image --severity CRITICAL --exit-code 1 → gate the push

Production cluster (continuous):
  trivy-operator → VulnerabilityReport + ConfigAuditReport CRDs
  ServiceMonitor → Prometheus → Grafana dashboard
  Alert: sum(trivy_image_vulnerabilities{severity="CRITICAL",namespace="production"}) > 0

The goal isn’t zero findings — that’s impossible with any real image. It’s a defined, enforced threshold: no CRITICAL unfixed in production, HIGH must be triaged within 7 days, dashboard shows the trend going down.


Summary

Trivy replaces a collection of specialized scanners with a single binary:

Task Command
Scan image for CVEs trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:latest
Scan Terraform trivy config ./terraform/
Scan Kubernetes manifests trivy config ./k8s/
Scan for secrets trivy fs --scanners secret .
Generate SBOM trivy image --format cyclonedx -o sbom.cdx.json my-app:latest
Scan live cluster trivy k8s cluster
Continuous cluster monitoring helm install trivy-operator aquasecurity/trivy-operator
CI gate on CRITICAL trivy image --severity CRITICAL --exit-code 1 my-app:latest

The Trivy Operator is what makes this sustainable at scale — instead of running scans manually or on a schedule, every workload in the cluster is continuously monitored and results are surfaced as Kubernetes resources you can query, alert on, and track over time.

Start with trivy image on your most critical services. Add the operator to your cluster. Wire up the Prometheus metrics. From there, the workflow drives itself.

Comments