Tools Used in DevSecOps · Trivy

Trivy

Trivy is Aqua Security's open-source, all-in-one scanner, and its whole reason for existing is coverage rather than any single deep specialty: one static Go binary that scans a container image, a filesystem, or a git repository for known vulnerabilities; checks Terraform, CloudFormation, and Kubernetes manifests for misconfigurations; scans for accidentally committed secrets; flags dependencies under risky licenses; and generates a software bill of materials — all from the same install, the same cache, and the same --severity/--exit-code conventions. Most pipelines used to need three or four separate tools to cover that ground: one for image vulnerabilities, one for Terraform, one for secrets, one for the SBOM. Trivy's pitch, and the reason it shows up as a default in so many CI configs, is that a platform team can pin one binary version instead of four and get most of what those four tools covered. By the end of this page you should know Trivy's internal architecture, how its two vulnerability-matching paths differ, how its misconfiguration scanner absorbed the standalone tfsec engine, the commands you'll actually run day to day, and where it still loses to a narrower, deeper tool in each of its four categories.

☺ Explain it like I'm 10

Imagine four different chores during one house inspection: checking whether anything in the fridge is expired, checking whether every door and window locks properly, checking whether a spare key was left somewhere obvious, and writing down a full list of everything in the house for the insurance company. You could hire four different specialists and coordinate four schedules — or you could hire one very well-trained inspector who carries one bag and does all four during the same walkthrough. Trivy is that inspector. It isn't necessarily better at any single chore than a specialist would be — but it shows up once, checks four things, and writes one report instead of four.

🦫Your host for this topic: Benny the Beaver — Trivy is Benny's own pre-flight check. He builds the container image, so he's the one who runs it against the image he just built, the Dockerfile that built it, and the Terraform sitting in the same repo — without reaching for three different binaries to do it.

What Trivy is, and the problem it solves

☺ Like you're 10: It started as one narrow tool and grew into four tools' worth of checks, all still living inside the same single binary.

Trivy started narrowly. It was created in 2019 by Teppei Fukuda (known in the project as knqyf263) as a container image vulnerability scanner, and Aqua Security — a cloud-native security vendor — took over as its primary steward not long after, continuing to develop it as free, open-source software under the Apache-2.0 license. What happened next is the part worth understanding: release by release, Aqua broadened Trivy's scope rather than shipping a second tool. Filesystem and git-repository scanning arrived. Misconfiguration scanning for infrastructure-as-code arrived. Secret scanning arrived. License scanning arrived. SBOM generation arrived. A Kubernetes-native mode (trivy k8s, and a separate in-cluster Trivy Operator) arrived for scanning a live cluster continuously rather than a single build artifact once. Each addition kept the same binary, the same cache directory, the same output-format family, and the same severity/exit-code conventions — which is the detail that actually matters here. Aqua didn't just add features; it kept every feature behind one consistent CLI surface.

The problem that consistency solves is an operational one, not a detection one. A pipeline that wants to know "is this image running a vulnerable OpenSSL," "does this Terraform module open a security group to the whole internet," "did someone bake an AWS key into a Docker layer," and "what's actually inside this artifact, for the audit trail" used to mean installing and maintaining three or four separate tools — each with its own install method, its own config file format, its own output schema, its own release cadence to track for CVEs in the scanner itself. Trivy answers all four questions from one static binary with no runtime dependency: drop it into a CI image with a single curl or a package-manager line, and every scan type is available immediately. That operational simplicity, more than raw detection depth in any one category, is why platform teams reach for it by default — see CNAPP & the unified cloud security stack for how this same "fewer tools, one correlated view" argument plays out one layer up, at the commercial-platform level.

◆ Key idea

Trivy's advantage is breadth wired through one consistent CLI, not that any single scanner inside it is the deepest tool in its category. Grype and Snyk push harder on vulnerability-matching nuance; Checkov ships a larger built-in policy library for infrastructure-as-code; gitleaks and TruffleHog do more with secrets specifically, including live-verifying a hit against the issuing provider. Trivy's bet is that covering four jobs adequately, from one binary, beats covering one job deeply and still needing three more tools installed for the rest. Whether that bet is right for a given team is exactly the question this page keeps coming back to.

Architecture: one binary, four scanners, one shared database

☺ Like you're 10: Trivy looks at what you point it at, decides which of its four checks apply, downloads the answer key it needs, and prints one combined report card.

Trivy has no server component by default and installs no agent — it's a single static binary you run against a target: a container image (local or in a registry), a directory on disk, a git repository URL, a running Kubernetes cluster, a virtual-machine image, or an already-generated SBOM file. Given a target, Trivy walks its filesystem, classifies what it finds — OS package manager databases, language-specific lockfiles, Terraform/CloudFormation/Kubernetes/Dockerfile/Helm source, anything that looks secret-shaped — and routes each category to the scanner built to handle it. Which scanners actually run is controlled by the --scanners flag (vuln, misconfig, secret, license), with sensible defaults per target type so most invocations don't need to specify it at all.

Vulnerability matching depends on data Trivy doesn't ship inside the binary itself. On first run — and periodically after, roughly every twelve hours unless told otherwise — Trivy pulls a vulnerability database published as an OCI artifact from ghcr.io/aquasecurity/trivy-db (plus a separate Java-specific index for jar-in-jar detection), aggregated from NVD, GitHub's Security Advisory database, and distro-specific trackers (Alpine's secdb, the Debian Security Tracker, Red Hat's OVAL feeds, Amazon Linux's ALAS), and caches it locally under ~/.cache/trivy. Misconfiguration checks, by contrast, ship bundled inside the binary itself as compiled Rego policies, so no network call is needed for that scanner to work offline. Secret and license scanning use their own bundled rulesets — a regex-and-entropy detector list for secrets, a package-license classifier for license scanning. One cache directory, one binary, four independent data sources feeding four independent checks that all report through the same output pipeline.

One target image · filesystem git repo · SBOM live k8s cluster trivy — one static Go binary --scanners selects which of the four run Vulnerability trivy-db, an OCI artifact pulled from ghcr.io and cached at ~/.cache/trivy OS pkgs + lockfiles Misconfiguration bundled Rego policies, compiled in — no network call needed to run absorbed tfsec's engine Secret bundled regex + entropy ruleset, customizable via trivy-secret.yaml working tree, not history License package license classifier, checked against a denylist compliance sign-off Unified report table · JSON · SARIF CycloneDX · SPDX One binary, one cache, one severity scale — four independently sourced checks reported through the same pipeline.

Vulnerability scanning: OS packages and language dependencies, matched two different ways

☺ Like you're 10: Checking your operating system's own software list works differently from checking the list of libraries your app imports — Trivy does both, and each one is matched against a different kind of answer key.

When Trivy scans an image or filesystem for known vulnerabilities, it's actually running two distinct matching processes, and knowing which one applies to a given finding matters when you're deciding how much to trust it. The first is OS package matching: Trivy identifies the base distribution (Alpine, Debian/Ubuntu, RHEL/CentOS/Fedora, Amazon Linux, and others) from distro-identifying files inside the image, then reads that distro's own package manager database directly — apk's installed-package list, dpkg's status file, rpm's database — without needing the apk, dpkg, or rpm binaries present in the image at all. Each installed package and version is matched against that specific distro's own security-advisory feed, not a generic CPE lookup against NVD. That distinction is why OS-level results tend to carry fewer false positives than a naive NVD/CPE matcher would produce: a distro's own advisory already states precisely which package version shipped the fix, instead of Trivy having to infer it from a loosely-matched product name.

The second path is language-dependency matching, and it runs independently of the OS layer entirely. Trivy parses lockfiles directly — no build execution required — and matches the parsed package-and-version pairs against the GitHub Advisory Database and OSV-sourced data bundled into the same trivy-db.

EcosystemWhat Trivy readsNote
npm / Yarn / pnpmpackage-lock.json, yarn.lock, pnpm-lock.yamlReads the resolved tree, not package.json's loose ranges
Pythonrequirements.txt, poetry.lock, Pipfile.lockUnpinned requirements.txt entries can't be version-matched precisely
Gogo.sum, and module metadata embedded in a compiled binaryCan identify dependencies even from a stripped binary with no source present
RubyGemfile.lock 
Javapom.xml (effective POM), fat/uber JAR contentsUses the separate Java vulnerability index for jar-in-jar detection
RustCargo.lock 
PHPcomposer.lock 

Because these two paths are independent, a scan can (and routinely does) return findings from both at once against a single image: a stale libssl reported through the OS-package path, and a vulnerable lodash reported through the npm-lockfile path, in the same run, in the same report. This is also the language-dependency job software composition analysis in depth covers generally — Trivy is simply one implementation of it, running for free, offline, inside the same binary as everything else on this page.

# scan a built image for OS + language vulnerabilities, fail the build on HIGH/CRITICAL
$ trivy image --severity HIGH,CRITICAL --exit-code 1 \
    --ignore-unfixed \
    registry.acme.io/checkout:1.8.2

# --ignore-unfixed matters in practice: without it, a build can fail on a CVE
# with no available patch yet, which nobody on the team can actually act on today.

Misconfiguration scanning: Terraform, CloudFormation, Kubernetes — and the tfsec absorption

☺ Like you're 10: tfsec used to be its own separate inspector for Terraform specifically; Aqua moved that inspector's whole rulebook inside Trivy instead of keeping two rulebooks that could drift apart.

Trivy's misconfiguration scanner walks infrastructure-as-code source and checks it against a bundled ruleset before anything is ever applied: Terraform (.tf/.tf.json, plus a rendered terraform plan converted with terraform show -json), CloudFormation templates, raw Kubernetes manifests (or a live cluster, via trivy k8s), Dockerfiles, rendered Helm charts, and Azure Resource Manager and Google Deployment Manager templates. Each provider gets its own parser, producing an internal representation that's checked against rules written in Rego — Open Policy Agent's policy language, the same language covered generally in IaC security & policy as code — organized by provider namespace, with each rule carrying an ID like AVD-AWS-0107 (an Aqua Vulnerability Database ID), a severity, a human-readable message, and remediation guidance.

The detail worth knowing precisely: this misconfiguration engine isn't something Aqua built from scratch for Trivy. tfsec was an independent, well-regarded open-source tool purpose-built for Terraform-specific static analysis. Aqua absorbed the tfsec project and its rule engine, and over subsequent releases folded that engine directly into trivy config's Terraform scanner rather than maintaining two separate parsers and two separate rulesets that could quietly drift out of sync with each other. Aqua has publicly signaled that the standalone tfsec binary is on a path toward being superseded by trivy config as the actively developed home for that logic — check the tfsec repository's own README for the current state of that timeline, since deprecation schedules for open-source projects tend to slip and shift. What matters for a team migrating is more concrete than the timeline: most tfsec-authored rule IDs and the familiar #tfsec:ignore:<ID> inline-suppression comment still work when the same Terraform runs through Trivy, because the engine underneath the two front ends is now the same code, not two reimplementations of the same idea. Swapping the CLI you invoke in CI is mostly a binary-name change, not a rewrite of every suppression comment in the repository. See the tfsec tool page for the standalone tool on its own terms.

# infra/security_group.tf — an open ingress rule, the kind a misconfig scan exists to catch
resource "aws_security_group" "web" {
  name = "web-sg"
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]   # AVD-AWS-0107 flags exactly this shape
  }
}
$ trivy config --severity HIGH,CRITICAL infra/
# infra/security_group.tf (terraform)
# ===================================
# Tests: 24 (SUCCESSES: 23, FAILURES: 1)
# AVD-AWS-0107 (HIGH): Security group rule allows ingress from 0.0.0.0/0.

# suppress one specific finding with a reason on record, same convention tfsec used
$ cat .trivyignore
# accepted risk — bastion host, restricted further by a security-group-referencing rule below
AVD-AWS-0107

SBOM generation and secret scanning: the other two scanners in the same binary

☺ Like you're 10: One more command from the same binary writes down everything inside the box; another checks nobody left a key taped to the outside of it.

Trivy generates a software bill of materials directly from a scanned artifact — the same command that finds vulnerabilities can emit the inventory those vulnerabilities were matched against, in either of the two dominant SBOM formats:

$ trivy image --format cyclonedx --output sbom.cdx.json registry.acme.io/checkout:1.8.2
$ trivy image --format spdx-json --output sbom.spdx.json registry.acme.io/checkout:1.8.2

# scan an SBOM someone else already generated — e.g. with Syft — for known vulnerabilities.
# this decouples "who produced the inventory" from "who checks it against a vuln feed."
$ trivy sbom sbom.cdx.json

That last command is worth pausing on: it means a pipeline can standardize on one SBOM generator — Trivy itself, or Syft, which many teams prefer specifically for SBOM generation — while still using Trivy purely as the vulnerability-matching engine against whatever inventory arrives. See software bills of materials for the fuller treatment of SPDX versus CycloneDX and why a real SBOM has to be generated from the built artifact rather than a source manifest.

Secret scanning runs as part of the same invocation by default, using a bundled set of regex-and-entropy detectors — AWS access keys, GCP service-account JSON, Slack tokens, private-key PEM headers, generic high-entropy strings — customizable through a trivy-secret.yaml config for org-specific patterns or exclusions. Keep the scope precise, though: Trivy's secret scanner inspects whatever filesystem or image layers it's given — the working tree, or a shallow checkout for trivy repo — not the full commit history a dedicated tool walks. gitleaks and TruffleHog exist specifically to crawl git history and, in TruffleHog's case, live-verify a hit against the issuing provider; Trivy's built-in detector is a useful last line of defense on the artifact you're about to ship, not a replacement for that discipline running earlier, on every commit.

# select exactly which scanners run in one invocation — vuln is the default alone if omitted
$ trivy fs --scanners vuln,secret,license .
$ trivy image --scanners misconfig registry.acme.io/checkout:1.8.2   # config-only, skip vuln DB entirely

Day-to-day commands

☺ Like you're 10: One target type per command, one severity/exit-code pair to actually gate a build, and a server mode so a whole CI fleet shares one warm cache instead of downloading the database a hundred times.

# the four target types you'll reach for most often
$ trivy image registry.acme.io/checkout:1.8.2        # a built container image
$ trivy fs .                                          # a local directory / checked-out repo
$ trivy repo https://github.com/acme/checkout          # clone-and-scan a remote git repo
$ trivy k8s --report summary cluster                    # scan every workload in the current kube-context

# gating flags — the ones that actually turn a scan into a pass/fail pipeline check
$ trivy image --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed registry.acme.io/checkout:1.8.2
# default exit code is 0 regardless of findings — forgetting --exit-code is the #1 reason
# a team thinks their pipeline is gating on Trivy when it has never actually failed a build

# machine-readable output for a dashboard, DefectDojo, or GitHub code scanning
$ trivy image --format sarif --output results.sarif registry.acme.io/checkout:1.8.2
$ trivy image --format json --output results.json registry.acme.io/checkout:1.8.2

# database lifecycle — matters for air-gapped and offline environments
$ trivy image --download-db-only                       # pre-warm the DB cache, no scan
$ trivy image --skip-db-update registry.acme.io/checkout:1.8.2   # don't touch the network at all
$ trivy image --offline-scan registry.acme.io/checkout:1.8.2      # scan using only what's already cached

# server mode — one shared cache for a whole CI fleet instead of every runner downloading its own
$ trivy server --listen 0.0.0.0:4954 &
$ trivy image --server http://trivy-server:4954 registry.acme.io/checkout:1.8.2

Exact subcommand and flag names have moved around more than once across Trivy's major versions — trivy config in particular has been reorganized as the project unified its CLI surface. If a tutorial's flags don't match what's installed, trivy --help and trivy <subcommand> --help are on the machine and current for the exact version running, which is the more reliable source than a blog post pinned to whichever release existed when it was written.

Gotchas and failure modes

☺ Like you're 10: Most of the surprises trace back to one of two things — the database has to come from somewhere over the network, or the thing you're scanning doesn't have the file Trivy needs to look at.

⚠ Watch out

Air-gapped and heavily network-restricted environments need the vulnerability and misconfiguration data staged in advance, not fetched at scan time. trivy image --download-db-only run somewhere with network access, followed by copying the cache directory (or running a local trivy server seeded from that cache) into the restricted environment, is the standard pattern — a scan that silently falls back to stale or missing data because the DB pull failed can report a clean image that isn't actually clean.

Where Trivy sits against Grype, Snyk, and standalone Checkov/tfsec

☺ Like you're 10: Every alternative trades Trivy's "does four jobs from one binary" for "does one job better" — which is the right trade depends entirely on which job matters most right now.

The real comparison is rarely "which tool is best" in the abstract — it's whether breadth from one binary or depth in one category is what a given pipeline stage actually needs.

ToolScopeStrengthTrade-off
TrivyVulnerability + misconfiguration + secret + license scanning, and SBOM generation, one binaryFour jobs from one install, one cache, one severity/exit-code convention"Adequate" depth in each category rather than class-leading in any one
Syft & Grype (Anchore)Vulnerability scanning, paired with a dedicated SBOM generatorFast, accurate OS + language vulnerability matching; the two concerns stay cleanly separated into two single-purpose toolsTwo binaries to install and pin instead of one; no misconfiguration, secret, or license scanning at all
SnykSCA, container, and IaC scanning, commercial platform with a metered free tierCurated, cloud-hosted vulnerability database with a research team behind it, plus computed fix suggestions and automatic pull requestsRequires network access and an account to run at all; paid tiers gate the deeper reachability and prioritization features — see the Snyk tool page for the full Trivy comparison
Checkov / standalone tfsecInfrastructure-as-code misconfiguration only, across more providersA larger built-in policy library than Trivy's config scanner covers out of the box; the actively maintained choice if IaC scanning is the whole jobNo vulnerability, secret, or SBOM capability at all — still needs two or three more tools to cover the rest of a pipeline

A pipeline built around depth in one category and breadth everywhere else is a reasonable, common shape: Trivy as the universal, always-on gate covering four jobs adequately at every commit, with a narrower, deeper tool layered on top of exactly the one category where a team has decided the extra depth is worth paying for — Snyk's Fix PRs on the repositories where developers actually act on them, or Checkov's larger IaC policy set once Terraform coverage specifically becomes the priority. See vulnerability management & triage for how findings from more than one of these tools get deduplicated into a single backlog once a pipeline is running more than one at once.

🎬 At the Shift-Left Squad
🦫

Benny the Beaver: One command, four checks — Trivy just scanned the image, the Dockerfile, and the Terraform folder in the same repo in about eight seconds. I used to run three separate tools for this.

🤖

Recon the Robot: The Terraform check is the interesting one for me. That's tfsec's old rule engine — it lives inside Trivy's config scanner now. I didn't have to touch my suppression comments to switch.

🐦

Pip the Hummingbird: While you're both in there — did it write the SBOM too? I don't care what scanned the image clean today. I care what's provably inside it a year from now.

🦫

Benny the Beaver: Same command, --format cyclonedx. One flag, not a second tool.

🦊

Foxy: Wait — the OS-package scan came back completely clean on this one. That base image is distroless, right? Clean because there's genuinely nothing wrong, or clean because there was nothing there for it to check?

🐢

Timmy the Turtle: That's exactly the question to ask before trusting a clean scan. No package database inside the image means no OS findings — full stop, regardless of what's actually running. The language-dependency scan still holds; the OS one is reporting "nothing to check," not "nothing wrong."

🦝

Rocky the Raccoon: Noted. If I ever want to hide something in an image, that's exactly the gap I'd go looking for first.

✓ Checkpoint

1. Name Trivy's four scanners, and what data source each one checks against. 2. Explain the difference between how Trivy matches an OS package's vulnerability versus a language dependency's vulnerability. 3. What exactly did Aqua absorb from tfsec, and what stayed working for a team that migrates from standalone tfsec to trivy config? 4. A container image comes back with zero OS-level vulnerability findings. What's the one question you should ask before trusting that as "this image is clean"? 5. Give one concrete reason a team might run Trivy and a narrower tool like Snyk or Grype, rather than picking exactly one.

Check your answers
  1. Vulnerability scanning, checked against trivy-db (an OCI artifact pulled from GHCR and cached locally); misconfiguration scanning, checked against bundled Rego policies; secret scanning, checked against a bundled regex-and-entropy ruleset; and license scanning, checked against a package license classifier.
  2. OS packages are enumerated by reading the distro's own package manager database (apk/dpkg/rpm) directly, then matched against that specific distro's own security-advisory feed — which tends to produce fewer false positives than a generic CPE match. Language dependencies are matched by parsing lockfiles directly (no build execution needed) against the GitHub Advisory Database/OSV data bundled in the same trivy-db. The two paths run independently and can both return findings in the same scan.
  3. Aqua absorbed tfsec's actual rule engine — the HCL parsing and rule logic — folding it directly into trivy config's Terraform scanner rather than maintaining two separate implementations that could drift apart. Most tfsec-authored rule IDs and the #tfsec:ignore:<ID> inline-suppression comment syntax still work, because the underlying engine is now shared rather than reimplemented twice — migrating is mostly a binary-name change in CI, not a rewrite of suppression comments.
  4. Whether the image has a real package manager database at all. Distroless and scratch-based images have nothing for the OS-package scanner to enumerate, so a clean OS-level result there means "nothing was there to check," not "nothing is wrong" — language-dependency findings are unaffected and still hold.
  5. Any reasonable answer citing Trivy's breadth-over-depth trade: for example, Snyk's computed fix suggestions and automatic pull requests aren't something Trivy does at all, or Checkov's larger built-in IaC policy library covers more misconfiguration patterns than Trivy's config scanner ships by default — a team can run Trivy as the free, universal, every-commit gate across all four categories and layer a narrower tool on top of specifically the one category where the extra depth earns its cost.