A container image that ships to production with an unpatched critical CVE is not a rare event. It is the default outcome unless someone builds a gate to stop it. Trivy container scanning gives teams that gate: an open-source scanner from Aqua Security that reads a container image, a filesystem, a Dockerfile, or a running Kubernetes cluster and reports exactly which packages carry known vulnerabilities, which secrets got baked in by accident, and which infrastructure-as-code files are misconfigured.

This tutorial builds a working Trivy container scanning pipeline from a blank terminal to a CI/CD gate that blocks a bad merge, plus a cross-check against Grype and a continuous scan of a live Kubernetes cluster. Every command below was tested against Trivy v0.74.0 (released August 14, 2026) and Grype v0.119.0 (released September 17, 2026), the current stable releases as of this writing. By the end you will have a repeatable workflow, not just a one-off scan.

Why Container Image Scanning Broke Out as a 2026 Priority

Software supply chain attacks stopped being a theoretical risk years ago, and the numbers from 2025 back that up. Sonatype’s Attacking the Assembly Line research logged 3,430 malware advisories tied to open source packages in 2025, a 3.69x jump over the roughly 931-advisory-per-year baseline recorded before generative AI tooling accelerated both attacks and defenses. Of the advisories Sonatype could classify, 47.3% were targeted attacks aimed at specific organizations or supply chains, up from just 2 to 4% a year during 2021 through 2024. Malicious packages built specifically to compromise a developer’s machine during install, not after deployment, made up 53% of the malware Sonatype tracked.

Runtime data tells a slightly better story. Sysdig’s 2025 Cloud-Native Security and Usage Report found that fewer than 17% of analyzed container images carried a high or critical vulnerability, and fewer than 6% of packages with a critical or high flaw were actually loaded into memory at runtime. That gap matters: it means most of what a scanner flags never gets executed, which is exactly why a scanning pipeline needs sane severity filtering rather than a blanket “fail on anything” rule. Get the filtering wrong and engineers start ignoring the scanner altogether.

MetricFigureSource
Malware advisories logged in 20253,430 (3.69x prior baseline)Sonatype, Attacking the Assembly Line (2026)
Targeted open source malware advisories47.3% of classified advisoriesSonatype, Attacking the Assembly Line (2026)
Container images with a high/critical CVEUnder 17%Sysdig, 2025 Cloud-Native Security and Usage Report
Critical/high vulnerabilities actually loaded at runtimeUnder 6%Sysdig, 2025 Cloud-Native Security and Usage Report
Cumulative malicious packages blockedOver 1.233 millionSonatype, State of the Software Supply Chain (2026)

Those figures explain why container image scanning moved from “nice to have” to a hard gate in most CI/CD pipelines. A tool that scans one image is a demo. A pipeline that scans every image, every Dockerfile, and every dependency, then blocks a merge automatically, is a control. The gap between the two is the difference between a security team that finds out about a vulnerable image from an auditor and one that finds out from its own build log.

None of this requires a large security team or an expensive platform. Trivy and Grype both run as free, open-source binaries, and the workflow this tutorial builds fits inside a standard CI runner with no additional infrastructure. That accessibility is part of why container image scanning shows up in nearly every modern DevSecOps checklist now, regardless of company size.

Trivy vs Grype: Choosing the Right Scanner for Your Pipeline

Trivy and Grype solve the same core problem from different starting points. Trivy, built by Aqua Security, scans container images, filesystems, git repositories, Kubernetes clusters, Terraform, and CloudFormation from one binary, and it bundles secret detection and misconfiguration checks alongside CVE matching. Grype, built by Anchore, focuses tightly on vulnerability matching against images and filesystems, and it pairs naturally with Syft, Anchore’s SBOM generator, when you want a two-tool pipeline with a clean separation between “generate the inventory” and “check the inventory against a CVE database.”

Running both in the same pipeline is not redundant. Vulnerability databases disagree at the margins because they pull from different upstream feeds and apply different matching logic, so a package that Trivy flags as fixed might still show up in Grype’s output for a day or two until its database syncs, or vice versa. Treat disagreement between scanners as a signal to double-check the advisory manually rather than a bug in either tool.

Where Grype Pulls Ahead

Grype’s database updates are fast and its output format is simple to parse, which makes it a good second opinion in a CI step that only needs a pass/fail signal. It also integrates cleanly with Syft-generated SBOMs, so if your pipeline already produces a CycloneDX or SPDX file for compliance reasons, Grype can scan that file directly instead of re-pulling the image.

CapabilityTrivy v0.74.0Grype v0.119.0
Image and filesystem CVE scanningYesYes
Dockerfile / IaC misconfiguration checksYesNo
Hardcoded secret detectionYesNo
SBOM generation (CycloneDX, SPDX)YesVia Syft companion tool
Kubernetes cluster scanningYes, via Trivy Operator v0.34.0Not built in
Single-binary installYesYes

For most teams the practical answer is to run Trivy as the primary gate because it covers images, IaC, and secrets in one pass, then run Grype as a lightweight cross-check on the final image before it ships. This tutorial builds exactly that setup.

Prerequisites and Exact Versions You’ll Need

You do not need a large environment to follow along, but pin these versions so your output matches the examples below. Check the Trivy release notes and the Grype release notes before you start, since both projects ship updates frequently.

  • Trivy v0.74.0 or newer (CLI, single binary, no separate database server required for the basic workflow)
  • Grype v0.119.0 or newer for the cross-check step
  • Docker Engine 27.x or a compatible OCI runtime (Podman 5.x also works for local image builds)
  • Git 2.40 or newer for the sample repository
  • A GitHub account with Actions enabled, or a GitLab account with CI/CD enabled, for the pipeline steps
  • kubectl configured against a test cluster (kind or minikube is fine) for the Kubernetes scanning step
  • Trivy Operator v0.34.0 for continuous in-cluster scanning

Nothing here requires a paid tier. Trivy and Grype are both fully open source under Apache 2.0, and the vulnerability databases they pull from are free to query, though heavy CI usage benefits from the caching step covered later.

Install Trivy and Run Your First Scan

Step 1: Install Trivy. On Linux or macOS, the fastest path is the official install script, which drops a pinned binary into your path. Full install options, including package manager repositories for every major distribution, live in the official Trivy documentation. Avoid piping curl straight into a root shell in production. Download the script, read it, then run it with a pinned version.

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

trivy --version
# Version: 0.74.0

On macOS with Homebrew, brew install trivy pulls the current formula. On Debian or Ubuntu, add Aqua Security’s apt repository instead of relying on the distro’s default package, which often lags several versions behind. If you would rather not install anything locally, the containerized form works everywhere Docker runs:

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy:0.74.0 image nginx:1.27

Step 2: Run your first image scan. Point Trivy at any public image to confirm the install works and to watch it pull its vulnerability database on first run, which takes a minute or two depending on your connection.

trivy image node:20-alpine

Expect output shaped like this, trimmed for length:

node:20-alpine (alpine 3.21.2)
=================================
Total: 4 (UNKNOWN: 0, LOW: 1, MEDIUM: 2, HIGH: 1, CRITICAL: 0)

Library      Vulnerability   Severity  Status   Installed  Fixed
libcrypto3   CVE-2025-XXXXX  HIGH      fixed    3.3.2-r0   3.3.3-r0

That single command already tells you more than most teams check before a deploy: what’s installed, what’s vulnerable, and whether a fix exists.

Read the Output: Severity, Exit Codes, and Formats

Step 3: Interpret severity levels and exit codes. Trivy classifies findings as UNKNOWN, LOW, MEDIUM, HIGH, or CRITICAL, pulled from the CVSS score reported by the upstream advisory. By default the CLI exits with code 0 no matter what it finds, which is useless in CI. You control the gate with two flags.

trivy image --severity HIGH,CRITICAL --exit-code 1 node:20-alpine
echo "Exit code: $?"

Now the process exits with 1 if any HIGH or CRITICAL finding shows up, and 0 otherwise, which is exactly the signal a CI job needs to fail a build. Trivy also supports --format json, --format sarif, --format cyclonedx, and --format table (the human-readable default), so pick the format the next tool in your pipeline expects rather than parsing plain text with regex.

SeverityTypical meaningCommon CI action
CRITICALActively exploitable, high impact, often remote code executionBlock merge, page on-call if in production image
HIGHSerious impact but harder to exploit or needs local accessBlock merge, ticket for next sprint if unfixed
MEDIUMLimited impact or requires unusual conditionsLog and track, do not block by default
LOW / UNKNOWNMinimal impact or unscored advisoryInformational only

Step 4: Filter noise with –ignore-unfixed. Given that Sysdig’s data shows under 6% of high/critical findings actually load at runtime, and a meaningful share of the rest have no available patch yet, blocking a build on every unfixed CVE just trains engineers to bypass the check. Add --ignore-unfixed so the gate only fires on vulnerabilities you can actually remediate today.

trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 myapp:latest

Track the unfixed findings separately in a dashboard rather than discarding them. A patch shipping next week can turn today’s “can’t fix it” into tomorrow’s blocked build if you never revisit the list. A simple weekly job that re-scans your currently deployed images against the latest database catches exactly this case, surfacing a fix that didn’t exist when you first shipped the image without requiring a new build to trigger the check.

Scan Dockerfiles and Catch Hardcoded Secrets

Step 5: Scan your Dockerfile for misconfigurations. A clean image can still ship from an insecure Dockerfile, running as root, exposing an unnecessary port, or baking a build argument into a layer where it persists after the build finishes. Trivy’s config scanner checks the Dockerfile itself, not just the built image.

trivy config --severity HIGH,CRITICAL ./Dockerfile

A typical finding flags a missing USER directive, meaning the container runs as root by default, or a COPY instruction with overly broad permissions. Fixing this class of issue at the Dockerfile level is cheaper than catching it after deploy, since it enforces container security posture before an image ever gets built. This ties directly into broader hardening practices, and if you have not locked down runtime privileges yet, our container security hardening walkthrough covers the cluster-side controls that complement image-level scanning.

Step 6: Catch hardcoded secrets before they ship. Trivy’s secret scanner runs against the same target and looks for patterns matching API keys, private keys, and cloud credentials accidentally committed into a layer.

trivy image --scanners secret myapp:latest

If this step ever finds a live cloud access key baked into a layer, rotate that key immediately, because rebuilding the image without the secret does not invalidate a key that already leaked. Leaked cloud credentials remain a routine finding in CI pipelines, which is exactly why one hardcoded key in a Dockerfile layer can turn a routine scan into an incident report.

Generate an SBOM With CycloneDX and SPDX

Step 7: Generate a software bill of materials. An SBOM is a structured inventory of every package, library, and dependency in your image, and it has become the standard artifact regulators, auditors, and downstream customers ask for. Trivy generates one directly, in either the CycloneDX or SPDX formats, both maintained as open standards.

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

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

Store both files as build artifacts alongside the image tag they describe. An SBOM without a clear link back to the exact image digest it covers is close to useless during an incident, when the first question is always “which of our running images contains this package.” Tag the SBOM filename with the image SHA, not just a version string, so a latest retag six months from now cannot orphan the record.

SBOMs also pay off outside a single incident response scenario. When a new zero-day drops in a widely used library, most teams’ first move is a frantic search through every repository asking “do we use this package, and where.” An archive of per-build SBOMs turns that into a single grep or a database query instead of a days-long audit across dozens of services, which is exactly the scenario CISA and other national cybersecurity agencies had in mind when they started pushing SBOM requirements into procurement rules.

Gate GitHub Actions and GitLab CI Pipelines

Step 8: Add Trivy to GitHub Actions with SARIF upload. The official action, aquasecurity/trivy-action, wraps the CLI and can upload results directly to GitHub’s code scanning tab in SARIF format, which surfaces findings inline on pull requests without a separate dashboard.

name: Container security scan

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read
  security-events: write

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

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

      - name: Scan image with Trivy
        uses: aquasecurity/[email protected]
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: HIGH,CRITICAL
          ignore-unfixed: true
          exit-code: '1'

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

Notice the if: always() on the upload step. Without it, a failed scan (which is the whole point of the job) skips the upload too, so you get a red build with no detail on why it failed. Pin the action to a specific version tag like 0.36.0 rather than @master, since a moving tag on a security-critical action is exactly the kind of dependency a supply-chain attacker targets.

Step 9: Add Trivy to GitLab CI. GitLab’s own container scanning template wraps a Trivy-based analyzer, but you can also invoke Trivy directly for tighter control over flags and output format.

container_scan:
  stage: test
  image:
    name: aquasec/trivy:0.74.0
    entrypoint: [""]
  variables:
    TRIVY_NO_PROGRESS: "true"
    TRIVY_CACHE_DIR: ".trivycache"
  cache:
    paths:
      - .trivycache/
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed
        --format json --output trivy-report.json
        "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
  artifacts:
    when: always
    paths:
      - trivy-report.json
    expire_in: 30 days

GitLab maintains its own container scanning documentation if you want the fully managed template instead of a hand-rolled job. The hand-rolled version above gives you the exact same severity filtering and exit-code behavior you already tested locally, which keeps local and CI results consistent, a detail that matters more than it sounds like when a developer says a build passed on their own machine.

Cache the Database and Suppress False Positives

Step 10: Cache the vulnerability database for faster builds. Every fresh Trivy run without a cache pulls the full vulnerability database, which adds 30 to 90 seconds to every CI job depending on network conditions. Persisting the cache directory between runs turns that into a small incremental update.

# GitHub Actions cache step, placed before the scan step
- uses: actions/cache@v4
  with:
    path: ~/.cache/trivy
    key: trivy-db-${{ runner.os }}-${{ github.run_id }}
    restore-keys: |
      trivy-db-${{ runner.os }}-

For high-volume pipelines running dozens of scans a day, standing up Trivy in client/server mode avoids redundant database downloads entirely, since every job talks to one shared server instead of maintaining its own local copy. That setup is covered in the advanced tips section below.

Step 11: Suppress false positives with .trivyignore and VEX. Not every finding deserves a block. If your team has manually verified that a flagged package is not reachable in your code path, or a patch genuinely does not exist and the risk is accepted, record that decision instead of silently working around the gate.

# .trivyignore
# CVE-2025-XXXXX: not exploitable, package loaded but function never called
# reviewed by security team 2026-08-01, revisit at next base image bump
CVE-2025-XXXXX

For a more auditable approach than a flat ignore file, use a VEX (Vulnerability Exploitability eXchange) document, which records not just that a CVE is suppressed but why, in a machine-readable format Trivy can consume with --vex. VEX matters more as your SBOM output starts feeding external audits, since “we ignored it” and “we assessed it as not exploitable, documented, and reviewed” are very different answers to give a customer’s security team.

Scan Kubernetes Continuously and Cross-Check With Grype

Step 12: Scan a running Kubernetes cluster continuously. A scan at build time catches problems in the image you’re about to deploy, but it says nothing about the images already running, some of which may be months old and vulnerable to CVEs disclosed after they shipped. Trivy Operator v0.34.0 closes that gap by running scans continuously inside the cluster and exposing results as native Kubernetes resources.

helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo update
helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system \
  --create-namespace \
  --version 0.34.0

kubectl get vulnerabilityreports --all-namespaces

If you already run an EKS cluster, the operator works the same way against managed control planes as it does against self-managed nodes, since it runs as a normal in-cluster workload rather than depending on cloud-provider-specific APIs. Watch the reports for images running with severities you already gate against in CI, since a running image that fails your build-time policy but somehow made it into production usually points to a gap in deployment controls, not the scanner.

Step 13: Cross-check results with Grype. Before a release candidate ships, run Grype against the same image as a second opinion. Discrepancies are rare but worth catching before, not after, a customer’s security team finds them independently.

grype myapp:latest --fail-on high --only-fixed -o json > grype-report.json

# Compare finding counts quickly
trivy image --severity HIGH,CRITICAL --ignore-unfixed -f json myapp:latest \
  | jq '.Results[].Vulnerabilities | length' | paste -sd+ | bc

Grype’s --fail-on high flag mirrors Trivy’s exit-code behavior, and --only-fixed is the Grype equivalent of Trivy’s --ignore-unfixed. Keeping the flag logic symmetric between the two tools makes the pipeline easier to reason about months from now when someone who didn’t write it has to debug why a build failed.

Build the Complete Vulnerability Management Workflow

Step 14: Tie every piece into one working project. Individually, each command above is a script. Together, they form a workflow: build, scan the Dockerfile, scan the image for CVEs and secrets, generate an SBOM, cross-check with a second scanner, and only then push to a registry. A Makefile keeps the sequence consistent whether a developer runs it locally or CI runs it on every push.

IMAGE := myapp:$(shell git rev-parse --short HEAD)

.PHONY: build scan-config scan-image scan-secrets sbom cross-check ship

build:
	docker build -t $(IMAGE) .

scan-config:
	trivy config --severity HIGH,CRITICAL --exit-code 1 ./Dockerfile

scan-image: build
	trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 $(IMAGE)

scan-secrets: build
	trivy image --scanners secret --exit-code 1 $(IMAGE)

sbom: build
	trivy image --format cyclonedx --output sbom-$(shell git rev-parse --short HEAD).json $(IMAGE)

cross-check: build
	grype $(IMAGE) --fail-on high --only-fixed

ship: scan-config scan-image scan-secrets sbom cross-check
	docker push $(IMAGE)
	@echo "All gates passed. $(IMAGE) shipped."

Wiring the Makefile into CI

Replace the individual scan steps in the GitHub Actions and GitLab CI jobs shown earlier with a single make ship call, and the exact same sequence runs whether a developer tests it on a laptop or a runner executes it in the cloud. That consistency is the real payoff of building a project instead of copy-pasting individual scan commands into a pipeline: nobody debugs a CI-only failure they can’t reproduce locally.

Run make ship once against a deliberately outdated base image (try node:16 instead of node:20-alpine) to confirm every gate actually fires before you trust it in production. If scan-config or scan-secrets passes silently when it shouldn’t, check that the exit code from each Trivy invocation is actually propagating, since a swallowed non-zero exit code in a shell pipeline is one of the most common ways teams discover their working gate never blocked anything.

5 Common Pitfalls That Undermine Container Scanning

  • Scanning only at build time, never in the running cluster. A CVE disclosed after deploy will not get caught until the next build, which might be weeks away. Pair CI scanning with the Trivy Operator step above.
  • Blocking on every severity instead of tuning to HIGH/CRITICAL plus –ignore-unfixed. Given that under 6% of high/critical findings actually load at runtime per Sysdig’s data, an overly broad gate trains developers to route around it rather than fix it.
  • Trusting a floating image tag like node:latest in production. The image behind that tag changes without your pipeline noticing, so today’s scan result says nothing about tomorrow’s pull. Pin to a digest, not just a tag.
  • Never rotating a secret Trivy finds, only rebuilding the image. A leaked credential is compromised the moment it’s committed, not the moment someone notices. Rebuilding without a secret does nothing to the old, already-leaked value.
  • Letting the .trivyignore file grow without review dates. An ignore list with no expiry silently accumulates risk, since nobody revisits an entry once it stops blocking builds. Require a reviewed-by and revisit-by note on every line, as shown in the example earlier.

Troubleshooting: 8 Errors You’ll Hit and How to Fix Them

  • “database download failed” on first run. Usually a firewall blocking access to the OCI registry Trivy pulls its database from. Check outbound access to ghcr.io, or mirror the database internally for air-gapped environments.
  • Scan hangs for minutes with no output. The database is downloading with no progress indicator in some terminals. Add --debug to confirm activity, or check network throughput directly.
  • CI job passes locally but fails in the pipeline, or vice versa. Almost always a database freshness mismatch. Pin the database update interval or cache it consistently, as shown in the caching step.
  • SARIF upload step fails with a permissions error. The job is missing the security-events: write permission block shown in the GitHub Actions example. Add it at the workflow or job level.
  • Exit code is always 0 even with critical findings. The --exit-code 1 flag is missing, or a shell pipeline is swallowing the real exit code. Test the command standalone before wrapping it in a script.
  • Grype and Trivy report different vulnerability counts for the same image. Expected behavior given different database sync timing. Treat a persistent, large gap as a signal to check both databases’ last-updated timestamps.
  • Trivy Operator shows no VulnerabilityReports after install. Give it a few minutes on first install, since it scans existing workloads asynchronously. Check operator pod logs in the trivy-system namespace if reports never appear after 10 minutes.
  • Secret scan flags a value that isn’t actually a secret. Pattern-based detection produces occasional false positives on high-entropy strings like hashes. Add the specific finding to .trivyignore with a note explaining why, rather than disabling the secret scanner entirely.

Advanced Tips for Production DevSecOps Pipelines

Once the basic gate is running reliably, a few refinements pay off at scale. Track findings over time rather than treating each scan as a one-off pass/fail event, since a security posture that’s improving matters more to stakeholders than a single green checkmark. Export JSON output to a time-series store and chart the trend of open HIGH/CRITICAL findings per service.

Set a base image update cadence too, since a stale base image accumulates new CVEs even when your own application code never changes. Rebuilding weekly against the latest patched base image, even with zero code changes, closes a class of vulnerabilities that a scan-only workflow can flag but never actually fixes on its own. Pair that cadence with automated dependency update tooling so the rebuild also picks up patched application-level packages, not just a refreshed base layer.

Running Trivy in Client/Server Mode at Scale

For organizations running hundreds of scans a day across many repositories, standing up a shared Trivy server avoids each job downloading its own database copy. Start the server once, then point every client at it.

# On a shared server
trivy server --listen 0.0.0.0:4954

# On every CI client
trivy image --server http://trivy-server.internal:4954 myapp:latest

This cuts both database bandwidth and per-job scan time significantly once you’re running enough parallel jobs that the shared cache pays for itself. It also gives you one place to configure database update policy instead of dozens of independent runners drifting out of sync.

Beyond that, layer scanning results into your existing alerting rather than building a parallel dashboard nobody checks. If your team already triages findings from a runtime tool, feed Trivy Operator’s VulnerabilityReports into that same queue instead of standing up a separate review process. And if you’re managing multiple container platforms, whether that’s comparing Docker against Podman for local builds or standardizing runtime choices across teams, apply the same scan gate regardless of which runtime built the image, since the vulnerability data lives in the image layers, not the tool that assembled them.

Finally, treat scanner findings from real incidents as a feedback loop into your pipeline, not just a one-time patch. Container escape and privilege-escalation flaws that occasionally surface in managed Kubernetes environments (this site has covered one such GKE container escape bug and a separate Docker authorization bypass CVE) are a reminder that image scanning is one layer, not the whole defense. It needs to sit alongside runtime protections and the kind of cluster hardening covered across the cloud computing hub on this site.

Frequently Asked Questions

Is Trivy free for commercial use?

Yes. Trivy is released under the Apache 2.0 license, and both the CLI and the vulnerability databases it queries are free to use, including in commercial CI/CD pipelines. Aqua Security also sells a commercial platform built around Trivy’s engine, but the open-source scanner itself carries no licensing cost.

How is Trivy container scanning different from Grype?

Trivy covers a wider surface out of the box, including Dockerfile misconfigurations, secret detection, and Kubernetes cluster scanning, while Grype focuses specifically on vulnerability matching and pairs with Syft for SBOM generation. Many teams, as this tutorial shows, run both: Trivy as the primary gate and Grype as a cross-check before release.

Should I block a build on every vulnerability Trivy finds?

No. Filter to HIGH and CRITICAL severities and add --ignore-unfixed, since Sysdig’s 2025 report found fewer than 6% of high/critical findings actually load into memory at runtime. Blocking on every finding regardless of severity or fixability tends to train engineers to bypass the gate rather than fix the underlying issue.

What’s the difference between a CycloneDX and an SPDX SBOM?

Both are open, machine-readable formats for listing every package in a software artifact, standardized through organizations like CycloneDX.org and the Linux Foundation’s SPDX project. CycloneDX originated in the application security community and includes richer support for vulnerability and license data, while SPDX has deeper roots in legal and compliance tooling. Trivy generates both formats, and which one you need often comes down to what your customers or auditors specifically request.

Does Trivy scan images running in a live Kubernetes cluster, not just at build time?

Yes, through Trivy Operator, which runs inside the cluster and continuously scans workloads, generating VulnerabilityReports and ConfigAuditReports as native Kubernetes custom resources. This catches CVEs disclosed after an image was already deployed, which a build-time-only pipeline would miss entirely until the next release.

How often does Trivy’s vulnerability database update?

The database updates multiple times a day upstream, and by default the CLI checks for a newer version on every run. In CI, cache the database directory between runs to avoid redundant downloads, and in a high-volume setup, run Trivy in client/server mode so one shared server manages database freshness for every job.

Can Trivy scan private registries and authenticated images?

Yes. Trivy reads standard Docker credential configuration, so if you’re already authenticated to a private registry through docker login or a CI-native credential helper, Trivy uses the same credentials automatically. No separate authentication setup is required beyond what your pipeline already uses to pull images.