Trivy
Trivy is a single small binary from Aqua Security that will look inside almost anything you can hand it — a container image, a folder of source code, a Git repository, a Terraform module, a virtual machine image, or an entire running Kubernetes cluster — and tell you what is broken in it: known vulnerabilities, dangerous misconfiguration, leaked credentials, and awkward software licences. It solves the platform problem of “we have four hundred images in production and nobody can answer what is inside them,” by making the answer cheap enough to produce on every single build, and machine-readable enough to fail the build automatically.
Imagine your school gets a delivery of two hundred lunchboxes every morning, packed by lots of different people. Some of them contain food that was recalled last week because it makes people sick. Nobody has time to open every box. Trivy is a very fast inspector who opens each box, reads every label, and compares it against a big list of recalls that gets updated several times a day. It shouts “this one has three bad items and two of them have a safe replacement available!” before the box ever reaches the lunch hall. It also notices when someone taped their house key to the inside of a box, and it writes down a full ingredient list for every box so that when a new recall is announced next month, you can find every affected lunchbox in seconds instead of days.
What Trivy is and the problem it solves
☺ Like you’re 10: It is one tool that opens up your software and compares what is inside against a list of known-bad things.
Trivy is an open-source (Apache 2.0) security scanner maintained by Aqua Security. It ships as one statically linked binary with no runtime dependencies, which is the single design decision that explains most of its popularity: you can drop it into any CI runner, any base image, or a colleague’s laptop with one curl and get a useful answer in under a minute. Everything else — the targets, the scanners, the output formats — is layered on top of that one convenience.
The problem: you cannot secure what you have not inventoried
A container image is a stack of filesystem layers, and inside those layers is a pile of software that almost nobody on your team chose deliberately. Your Dockerfile said FROM node:20, and that pulled in a Debian userland with several hundred OS packages, plus whatever npm install resolved from your lockfile, plus whatever those packages depend on. Meanwhile the world publishes thousands of new CVEs a month against exactly that software. The gap between “we built this image four months ago and it worked” and “this image contains a remotely exploitable flaw in a library you have never heard of” opens silently and gets wider every day.
Before scanners, teams answered this with a spreadsheet and a quarterly audit, which is to say they did not answer it. Trivy closes the gap by making the inventory automatic and the comparison continuous: it identifies every OS package and every language dependency it can find, matches them against a regularly refreshed vulnerability database, and reports which findings actually have a fixed version available — the only ones you can immediately act on.
One binary, many targets
The thing that distinguishes Trivy from a plain image scanner is the breadth of what it will accept as a target. The same binary, the same databases, and the same output formats apply across all of them, which means one tool and one report format covers the whole supply chain from a developer’s working directory to a production cluster.
| Subcommand | Target | What it finds there |
|---|---|---|
trivy image | A container image (registry, local Docker/Podman daemon, or a .tar via --input) | OS package + language dependency CVEs, secrets, licences |
trivy fs | A local filesystem path — source tree, unpacked artifact | Lockfile dependencies, secrets, misconfiguration in IaC files |
trivy repo | A remote Git repository URL (optionally at a branch, tag or commit) | The same as fs, without cloning by hand |
trivy config | IaC directory: Dockerfile, Kubernetes YAML, Terraform, Terraform plan JSON, CloudFormation, Helm charts, Azure ARM | Misconfiguration only |
trivy k8s | A live cluster, via your current kubeconfig context | Workload images, manifests, cluster infrastructure and RBAC |
trivy vm | A VM image or disk snapshot (AMI, VMDK, raw) | OS packages and language dependencies inside the disk |
trivy sbom | An existing CycloneDX or SPDX SBOM file | Vulnerabilities, without re-scanning the artifact |
What it is not
Trivy is a static scanner. It inspects artifacts and configuration at rest; it does not watch a running container’s syscalls, so it will never tell you that something is currently spawning a shell in production — that is Falco’s job. It does not enforce anything either: it produces findings and an exit code, and something else has to act on them, whether that is a pipeline step or an admission controller like Kyverno or OPA Gatekeeper. And it does not sign anything — proving an image is the one you scanned is Sigstore and cosign’s territory. Trivy tells you what is inside the box; other tools decide whether the box may be opened, and whether it is really your box.
Where it fits in a platform
☺ Like you’re 10: It sits in two places — on the assembly line where things get built, and inside the cluster where things are already running.
Trivy belongs to the platform’s supply-chain and governance concern, and it is unusual in that it earns its keep in two quite different planes. In the delivery plane it runs as a CI step: fast, blocking, and opinionated, refusing to let a bad artifact reach the registry. In the runtime plane it runs as the Trivy Operator: continuous, non-blocking, and observational, because the image you shipped clean in March is not clean in June and nothing about it changed except the world.
Shift left and shift down — two placements
The CI placement is the one that prevents problems. Scan the image you just built, before you push it, with a severity threshold and a non-zero exit code, and the broken artifact never exists in a place anyone can deploy from. The operator placement is the one that catches the problems you could not have prevented: a CVE disclosed after the build. Neither replaces the other, and platforms that run only the first develop a slow, invisible rot. Both feed the same story your Governance & Compliance evidence needs to tell.
Its neighbours
Upstream sits your pipeline — Tekton, Argo Workflows, or a hosted CI — described on CI/CD & Progressive Delivery. Trivy is a task in it. Directly beside it sits cosign, and the pairing matters more than either tool alone: Trivy produces an SBOM and a vulnerability report, cosign signs and attests them, and at admission time Kyverno’s verifyImages rules refuse anything without a valid attestation. That is a complete, closed supply chain — build, inspect, attest, verify — and it is the shape Security & Policy Enforcement asks you to be able to describe. Downstream, the operator’s reports land as Kubernetes objects, so Prometheus can alert on them and GitOps delivers the operator itself like any other add-on. And Trivy’s config scanner overlaps deliberately with policy engines: it catches a privileged pod in a manifest at commit time, hours before Kyverno would have caught it at admission time.
CNPE domain relevance
Trivy is not on the official CNPE tool list — but scanning is an exam competency. Domain 5, Security & Policy Enforcement, expects you to reason about supply-chain security, image provenance, vulnerability management and the SBOM, and Domain 2 expects you to describe where those checks belong in a delivery pipeline. Trivy is simply the most common concrete answer, so it is worth knowing as a pattern even though no task will name it. The lesson to study first is Security & Policy Enforcement; the wider map of what is and is not on the tool list lives on The Tool Landscape.
How it works — architecture, databases, and CRDs
☺ Like you’re 10: It unpacks the thing, makes a list of everything inside, then checks the list against a recall notice it downloads a few times a day.
Trivy’s scan is a short pipeline, and understanding it explains almost every surprising result you will ever get from it.
The four scanners
Trivy runs up to four independent scanners over whatever it analysed, and you choose them with --scanners. Which ones run by default depends on the target, which is the single most common source of “why did it not find that?” — misconfiguration scanning, for instance, does not run on trivy image unless you ask.
--scanners value | Looks for | Where it applies |
|---|---|---|
vuln | Known CVEs in OS packages (apk, dpkg, rpm) and language dependencies read from lockfiles — package-lock.json, go.sum, Gemfile.lock, poetry.lock, jar metadata | image, fs, repo, k8s, vm, sbom |
misconfig | Rego-based checks over IaC: a Dockerfile with USER root, a Pod with privileged: true, a public S3 bucket in Terraform, a Helm chart with no resource limits | fs, repo, config, k8s |
secret | Credentials committed or baked into a layer — AWS keys, GitHub tokens, private keys — matched by built-in regex rules | image, fs, repo (on by default) |
license | Declared and detected software licences, normalised to SPDX identifiers; --license-full also scans file headers | image, fs, repo |
The databases it downloads
Trivy is only as current as its data, and it keeps that data outside the binary. On first run it pulls the vulnerability database as an OCI artifact — published to ghcr.io/aquasecurity/trivy-db, with public registry mirrors used as fallbacks — into its cache at ~/.cache/trivy, and re-downloads it once the cached copy goes stale (upstream rebuilds the database several times a day). There is a separate Java database for identifying jars that carry no useful metadata, and a checks bundle containing the misconfiguration policies. Everything about air-gapped operation, registry rate limits and reproducible scans comes back to these downloads, so hold on to that fact — it returns in the gotchas.
A vulnerability scan is a join between two things that change at different speeds: your artifact, which is frozen the moment you build it, and the vulnerability database, which changes hourly. That asymmetry is the whole reason continuous re-scanning exists. Nothing about the image needs to change for it to become dangerous — and it is also why a scan result should always be recorded with a timestamp and a database version, not treated as a permanent property of the image.
Trivy Operator and the CRDs it introduces
The Trivy Operator is a separate deployable that puts Trivy inside the cluster as a controller. It watches workloads, launches scan jobs for the images they reference, and writes the results back as Kubernetes custom resources owned by the workload that produced them. That last detail is what makes it feel native: the reports garbage-collect themselves when the workload is deleted, and you query them with plain kubectl.
| Custom resource | Short name | What it records |
|---|---|---|
VulnerabilityReport | vulns | CVEs found in one container of one workload, with severity counts in report.summary |
ConfigAuditReport | configaudit | Misconfiguration checks against the workload’s own manifest |
ExposedSecretReport | exposedsecrets | Credentials discovered baked into the image’s layers |
SbomReport | sbomreport | The component inventory for a container, stored in-cluster |
RbacAssessmentReport / InfraAssessmentReport | — | Over-broad RBAC bindings; control-plane component hardening |
ClusterComplianceReport | compliance | Rolled-up posture against a benchmark such as CIS or NSA hardening |
The resources you will actually write
☺ Like you’re 10: Three files: one that makes the build fail, one that says which complaints to ignore, and one that installs the in-cluster watcher.
A CI gate that fails the build
This is the artifact that matters most, and the four lines carrying the weight are --severity, --ignore-unfixed, --exit-code 1 and the ordering: scan before push, so a failing artifact never reaches a place anyone can deploy from. Note the two-pass pattern — a full informational report that never fails, then a narrow blocking gate — which gives developers the whole picture without making the whole picture blocking.
# .github/workflows/build.yaml — build, scan, SBOM, then push
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # needed to upload SARIF
steps:
- uses: actions/checkout@v4
# the runner needs the trivy binary before the steps below — install it
# with Aqua's setup action, your package manager, or a prebuilt image
- name: Build (do NOT push yet)
run: docker build -t "$IMAGE:$GITHUB_SHA" .
env: { IMAGE: ghcr.io/acme/checkout }
# ---- pass 1: everything, informational, never blocks ----
- name: Full report (SARIF for code scanning)
run: |
trivy image \
--scanners vuln,secret,license \
--format sarif --output trivy.sarif \
--exit-code 0 \
"ghcr.io/acme/checkout:$GITHUB_SHA"
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: trivy.sarif }
# ---- pass 2: the gate ----
- name: Fail on fixable CRITICAL/HIGH
run: |
trivy image \
--severity CRITICAL,HIGH \
--ignore-unfixed \
--exit-code 1 \
--no-progress \
"ghcr.io/acme/checkout:$GITHUB_SHA"
# ---- SBOM as a build artifact, published alongside the image ----
- name: Generate SBOM
run: |
trivy image --format cyclonedx \
--output sbom.cdx.json \
"ghcr.io/acme/checkout:$GITHUB_SHA"
- uses: actions/upload-artifact@v4
with: { name: sbom, path: sbom.cdx.json }
- name: Push, then sign and attest
run: |
docker push "ghcr.io/acme/checkout:$GITHUB_SHA"
cosign sign --yes "ghcr.io/acme/checkout:$GITHUB_SHA"
cosign attest --yes --type cyclonedx \
--predicate sbom.cdx.json \
"ghcr.io/acme/checkout:$GITHUB_SHA"A Trivy step with no --exit-code 1 prints a beautiful red table and then exits 0. The pipeline goes green, everyone assumes they are protected, and nothing is blocked — the scanning equivalent of leaving a policy in audit mode forever. If you add exactly one flag to a pipeline today, add that one. Prove it works the way you prove any control works: deliberately build an image with a known-vulnerable base and watch the build go red.
trivy.yaml and the ignore files
Once a scan has more than three flags, move it into a config file so the same settings apply on a laptop and in CI. Trivy reads trivy.yaml from the working directory automatically (or --config elsewhere), and every CLI flag has a config-file equivalent and a TRIVY_-prefixed environment variable. Alongside it sits the ignore file — and the newer YAML form is strictly better than the flat list, because it forces two things a plain .trivyignore cannot: a reason and an expiry date.
# trivy.yaml — checked into the repo, used by CI and laptops alike
severity:
- CRITICAL
- HIGH
scan:
scanners:
- vuln
- secret
- misconfig
skip-dirs:
- vendor
- test/fixtures
vulnerability:
ignore-unfixed: true # only findings we can actually act on
exit-code: 1
db:
repository: registry.internal.acme.io/mirror/trivy-db # our pull-through mirror
cache:
dir: /var/cache/trivy
# ---------------------------------------------------------------
# .trivyignore.yaml — exceptions with an owner and an expiry date
vulnerabilities:
- id: CVE-2024-12345
paths:
- usr/local/bin/legacy-helper
statement: "Not reachable: binary is never executed in this image."
expired_at: 2026-09-30 # after this date the finding returns
- id: CVE-2023-45678
statement: "Awaiting upstream base-image rebuild, tracked in PLAT-4417."
expired_at: 2026-08-15
misconfigurations:
- id: AVD-KSV-0118 # default securityContext, accepted for jobs
statement: "Batch namespace only; enforced instead by Kyverno at admission."
expired_at: 2026-12-31“The thing that made me stop hating the scanner was --ignore-unfixed. Before that, my build failed with ninety findings and zero of them had a fix available, so the only possible action was to switch the check off. Now it fails with two, both have a version number in the ‘Fixed Version’ column, and I bump them in five minutes. Same tool, completely different relationship.”
Installing the operator and reading what it produces
The operator ships as a Helm chart, so it goes into your config repo and is delivered by Argo CD or Flux like any other cluster add-on. The two values worth thinking about are the scan job resource limits (scan jobs are real pods and they compete for capacity) and the report TTL.
# values.yaml for the trivy-operator Helm chart
targetNamespaces: "" # "" = all namespaces
trivy:
ignoreUnfixed: true
severity: CRITICAL,HIGH,MEDIUM
dbRepository: registry.internal.acme.io/mirror/trivy-db
operator:
scanJobsConcurrentLimit: 5 # do not stampede the cluster
scanJobTTL: "10m"
vulnerabilityScannerScanOnlyCurrentRevisions: true # skip old ReplicaSets
configAuditScannerEnabled: true
exposedSecretScannerEnabled: true
metricsFindingsEnabled: true # export finding counts to Prometheus
serviceMonitor:
enabled: true
# ---------------------------------------------------------------
# What lands in the cluster afterwards (abridged, read-only output):
apiVersion: aquasecurity.github.io/v1alpha1
kind: VulnerabilityReport
metadata:
name: replicaset-checkout-7d9f4c8b6-checkout
namespace: checkout
labels:
trivy-operator.resource.kind: ReplicaSet
trivy-operator.resource.name: checkout-7d9f4c8b6
trivy-operator.container.name: checkout
ownerReferences: # deleted automatically with the workload
- apiVersion: apps/v1
kind: ReplicaSet
name: checkout-7d9f4c8b6
report:
artifact: { repository: acme/checkout, tag: "1.4.3" }
registry: { server: ghcr.io }
scanner: { name: Trivy, vendor: Aqua Security, version: 0.58.0 }
summary:
criticalCount: 0
highCount: 2
mediumCount: 11
lowCount: 34
vulnerabilities:
- vulnerabilityID: CVE-2024-45491
resource: libexpat
installedVersion: 2.5.0-1
fixedVersion: 2.5.0-1+deb12u1 # actionable: a fix exists
severity: HIGHDay-to-day commands
☺ Like you’re 10: A handful of commands you will type over and over, and one that saves you when the internet is not there.
Scanning things
# --- images --- trivy image nginx:1.27 # from a registry (or local daemon) trivy image --input app.tar # from a saved tarball, no daemon trivy image --severity CRITICAL,HIGH --ignore-unfixed --exit-code 1 acme/checkout:1.4.3 trivy image --scanners vuln,secret,license --license-full acme/checkout:1.4.3 trivy image --pkg-types os acme/checkout:1.4.3 # OS packages only, skip app deps # --- source and repos --- trivy fs . # this working directory trivy fs --scanners secret,misconfig ./deploy # what a pre-commit hook should run trivy repo https://github.com/acme/checkout --branch main # --- infrastructure as code --- trivy config ./terraform # Terraform, CloudFormation, ARM trivy config ./deploy/k8s # plain Kubernetes manifests trivy config --severity HIGH,CRITICAL ./charts/checkout # a Helm chart # --- a whole cluster (uses your current kubeconfig context) --- trivy k8s --report summary # posture at a glance trivy k8s --report all --include-namespaces checkout,payments # rolled up against a benchmark — built-in spec ids are versioned, # so check `trivy k8s --help` for the ones your build actually ships trivy k8s --compliance k8s-cis-1.23 --report summary # --- output shapes --- trivy image --format json --output report.json acme/checkout:1.4.3 trivy image --format sarif --output trivy.sarif acme/checkout:1.4.3 trivy image --format table --scanners vuln acme/checkout:1.4.3 # the default
SBOMs — producing, consuming, and re-scanning
This is the workflow that pays off later. Generate the SBOM once at build time, publish it as an artifact and as a signed attestation, and then when a new CVE lands you re-scan the SBOM — which takes milliseconds and needs no registry access — instead of pulling and re-analysing hundreds of images.
# generate — two industry-standard formats, from the same scan trivy image --format cyclonedx --output sbom.cdx.json acme/checkout:1.4.3 trivy image --format spdx-json --output sbom.spdx.json acme/checkout:1.4.3 trivy fs --format cyclonedx --output src-sbom.json . # SBOM of a source tree # consume — scan an SBOM someone else produced, offline and instant trivy sbom sbom.cdx.json trivy sbom --severity CRITICAL --exit-code 1 sbom.cdx.json # convert a saved JSON result into another shape without re-scanning trivy convert --format table --output report.txt report.json # pair it with signing, so consumers can trust the inventory cosign attest --yes --type cyclonedx --predicate sbom.cdx.json ghcr.io/acme/checkout:1.4.3 # keyless verification must state WHICH identity you will accept, or it proves nothing cosign verify-attestation --type cyclonedx \ --certificate-identity-regexp 'https://github.com/acme/.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/acme/checkout:1.4.3
Databases, caches and air-gapped runs
# pre-warm the cache (do this once in a base image or a CI cache step) trivy image --download-db-only trivy image --download-java-db-only # use a pre-seeded cache: no database downloads, no external dependency lookups # (the image itself is still pulled from its registry) trivy image --cache-dir /var/cache/trivy --skip-db-update --skip-java-db-update \ --offline-scan acme/checkout:1.4.3 # mirror the databases into your own registry (air-gapped platforms): # pull the OCI artifact once, then oras push it to the internal registry oras pull ghcr.io/aquasecurity/trivy-db:2 trivy image --db-repository registry.internal.acme.io/mirror/trivy-db nginx:1.27 # client/server mode: one server holds the DB, many thin clients scan trivy server --listen 0.0.0.0:4954 trivy image --server http://trivy.platform.svc:4954 acme/checkout:1.4.3 # in-cluster reports, once the operator is running kubectl get vulnerabilityreports -A kubectl get vulns -n checkout -o wide kubectl get configauditreports -A kubectl get exposedsecretreports -A
Client/server mode is the underrated feature for a platform team. Instead of every CI runner downloading a multi-hundred-megabyte database several times a day — burning bandwidth and hitting registry rate limits — you run one trivy server in the cluster that holds the database, and runners send it a package list. Scans get faster, the network gets quieter, and you gain one central place to observe what is being scanned.
Gotchas and failure modes
☺ Like you’re 10: Four ways this tool disappoints people, and all four are avoidable.
The noise problem — unfixed CVEs
The fastest way to get a scanner switched off is to make it fail builds for things nobody can fix. Distributions like Debian and Ubuntu track large numbers of CVEs they have assessed as low-priority and marked will-not-fix; they show up in a raw scan with no fixed version, sometimes dozens at a time, and no amount of developer effort will make them go away. --ignore-unfixed is the correct default for a blocking gate: fail only on findings where a fixed version exists, and report the rest without blocking. Then reduce the base-image surface — a distroless or Alpine base removes hundreds of packages, and packages you do not ship cannot have CVEs. Handle the genuinely irreducible exceptions in .trivyignore.yaml with a statement and an expiry, never by widening the severity threshold.
Databases, air gaps and rate limits
Trivy without a current database is a false sense of security, and this is where CI breaks in ways that look unrelated. Symptoms: intermittent scan failures pulling ghcr.io/aquasecurity/trivy-db because a shared CI egress IP hit an anonymous registry rate limit; a totally clean report from an air-gapped runner that simply never got a database; a slow pipeline because every one of forty parallel jobs downloads the same database. The fixes are all boring and all durable: mirror the DB into your own registry and point at it with --db-repository, pre-seed a shared cache directory, authenticate to the registry with TRIVY_USERNAME/TRIVY_PASSWORD, or run client/server mode. In a genuinely disconnected environment, combine --skip-db-update with a cache you refresh deliberately — and treat that refresh as a scheduled job someone owns, because a stale database fails silently. When a scan behaves strangely in a pipeline, this and the container-runtime access below are the first two things to check; the wider method is on Triage: Delivery.
Scanning the wrong thing
Trivy will cheerfully scan a target that is not what you meant and return a confident, useless result. trivy fs . on a source tree finds lockfile dependencies but not the OS packages of the base image you will build on top of — it does not know a Dockerfile is coming. trivy image myapp:latest in a pipeline may resolve a stale image already in the local daemon rather than the one you just built; scan by digest or by the exact immutable tag. Scanning an image that has been squashed or rebuilt after the scan means you certified different bytes than you shipped, which is precisely the hole that signing the digest closes. And trivy config on a directory of Helm templates may parse very little, because unrendered templating is not YAML — render the chart first and scan the output. In every case the discipline is the same: scan the exact artifact you will ship, identified by digest.
The quieter traps
Language dependency detection needs lockfiles: no package-lock.json or go.sum means an incomplete inventory, and Trivy will not tell you it is missing. Multi-stage builds can leave build-time secrets in a discarded layer that never reaches the final image — trivy image sees only the final one, so scan the source with trivy fs too. Severity ratings come from the upstream source and the same CVE can be rated differently by NVD and by a distribution, so two scanners disagreeing is normal rather than a bug. The operator’s scan jobs need pull credentials for private registries or every report comes back empty. And on a large cluster, thousands of report objects are thousands of real etcd entries — cap scanJobsConcurrentLimit, scan only current revisions, and watch the count. If workload symptoms follow an operator rollout, start on Triage: Workloads.
Install Trivy locally and run three experiments. One: trivy image alpine:3.14 — an old base — then trivy image alpine:3.21, and watch the finding count collapse; that is the argument for base-image hygiene, in one command. Two: add --ignore-unfixed --severity CRITICAL,HIGH --exit-code 1 to the first, run echo $?, and confirm it is 1 — you have just built a CI gate. Three: generate an SBOM with --format cyclonedx, then run trivy sbom against the file with the network switched off, and notice how fast it is. Then commit a fake AWS key to a scratch repo and run trivy fs . to watch the secret scanner catch it. Four experiments, and the whole tool clicks.
Alternatives and when to choose it
☺ Like you’re 10: Other inspectors exist. Most do one job well; Trivy does several jobs adequately, which is usually what a platform wants.
Trivy is not the only scanner, and the honest case for it is breadth and friction, not depth. A specialist will beat it on any single axis; almost none of them will cover images, source, IaC, secrets and clusters from one binary with one report format.
| Tool | Shape | Strengths | Reach for it when |
|---|---|---|---|
| Trivy (Aqua) | One binary, many targets, four scanners | Zero-install, fast, image + IaC + secrets + SBOM + cluster operator, excellent CI ergonomics | You want one tool and one report format across the whole supply chain — the usual platform default |
| Grype + Syft (Anchore) | Two focused binaries: Syft builds the SBOM, Grype scans it | Very clean separation of inventory from matching; Syft is arguably the best SBOM generator | You want SBOM generation as a first-class, independently governed step |
| Clair (Red Hat) | A server with an API, indexes registry content | Registry-integrated, continuous re-indexing at scale (it powers Quay) | You operate a registry and want scanning as a registry service, not a CLI |
| Checkov | IaC misconfiguration only | Deeper Terraform coverage, graph-based checks, richer custom policy authoring | Your primary risk is cloud infrastructure, not container contents |
| Snyk and commercial suites | SaaS platform | Curated advisory data, reachability analysis, fix pull requests, licence governance, support | You need vendor-backed data quality and remediation workflow, and can pay per seat |
| Kyverno / Gatekeeper | Admission control — not a scanner | Blocks at the cluster door, enforces signatures and manifest rules | Always, as well as a scanner — they answer different questions |
A practical rule
Start with Trivy in CI, because the first scan you actually run beats the perfect scan you are still evaluating. Add the operator once CI scanning is boring, so you catch post-build disclosures. Add a specialist only when a specific gap hurts — Syft if SBOM fidelity becomes a compliance requirement, Checkov if Terraform is where your real risk lives. Note that one former specialist is already inside the box: tfsec, the Terraform scanner, was deprecated by Aqua and its checks folded into Trivy’s misconfiguration scanner, which is why trivy config covers Terraform at all. And never let the scanner substitute for admission control: Trivy stops a bad image from being published, while Kyverno stops an unverified image from being run. You want both doors locked.
Foxy: Good news — I added Trivy to every pipeline this morning. We are officially secure. ✅
Timmy: Does any of them use --exit-code 1?
Foxy: …it prints a really impressive red table?
Timmy: Then it is wallpaper, not a gate. Every build is passing with findings in it. One flag.
Dot: Please also add --ignore-unfixed. Last week it failed my build on sixty CVEs and not one had a fix. I nearly deleted the step out of spite.
Gizmo: Or — hear me out — --severity CRITICAL only, and we add * to .trivyignore for the rest. Green boards by lunchtime! 🤑
Timmy: No. Exceptions get a written reason and an expiry date, or they are permanent by accident. And Foxy — scan the digest you push, not :latest, or we are certifying bytes we never shipped.
Benny: I will mirror the DB into our registry too. Forty runners each pulling it four times a day is why CI was flaky on Tuesday.
Exam relevance and going further
☺ Like you’re 10: No exam task will say “Trivy” — but the ideas behind it absolutely turn up, and you cannot open its website that day.
Trivy is not on the official CNPE tool list, so do not expect a task that names it. What is examinable is the competency it represents: knowing that supply-chain security means scanning artifacts for known vulnerabilities, generating and consuming an SBOM, gating a pipeline on the result, and pairing scanning with signature verification at admission. Expect that to appear as a scenario or design question — “where in this pipeline would you place a vulnerability check, and what should it do when it finds something?” — rather than as a command to type. The answer worth internalising: scan the artifact before it is pushed, fail on fixable high-severity findings, publish the SBOM, sign the digest, and verify the signature at admission.
The documentation allowlist — read this twice
During the CNPE the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, task-specific documentation explicitly linked in the exam’s Quick Reference box, and local man pages and /usr/share docs on the exam machine. trivy.dev and aquasecurity.github.io are not on that list, and neither is any SBOM specification site. If a Trivy binary happens to be present on an exam machine, trivy --help and trivy image --help are local and therefore fair game — but do not plan around it. Anything you need on the day must be in your head. Drill the shapes that are examinable on Know Cold, and read the allowlist rules in full on The Docs Map. Confirm the current allowlist on the Linux Foundation’s own exam pages in the days before you sit.
⚖ CNPA vs CNPE — That allowlist is a CNPE-only mechanic: CNPE is hands-on, so it permits those narrow live lookups mid-task. CNPA is stricter, not looser — a fully closed-book, multiple-choice exam with zero external resources and zero lookups of any kind, so neither trivy.dev nor kubernetes.io would be reachable there either. Even so, the concept-level knowledge above — the scan-SBOM-sign-verify loop, the four scanner types, why a clean scan expires — is exactly the kind of thing CNPA's closed-book recall draws on.
What to be able to do without notes
Explain in one sentence why a scan result expires even though the image never changes. Name the four scanner types and give an example finding from each. Write a CI step that fails only on fixable CRITICAL and HIGH findings, and name the three flags that make it do so. Say what an SBOM is, name the two standard formats (CycloneDX and SPDX), and explain why you would scan the SBOM rather than the image when a new CVE drops. Describe the difference between scanning in CI and scanning continuously in the cluster, and why you want both. Name the three main Trivy Operator report kinds. And be able to draw the closed loop: build → scan → SBOM → sign → verify at admission — because that loop, not the tool, is what the exam is testing. Practise the surrounding concepts on Practice: Security and the pipeline context on CI/CD & Progressive Delivery.
Official resources for after the exam
Outside the exam, the canonical sources are trivy.dev/docs (the Target and Scanner sections are the two to read end to end), the source and issue tracker at github.com/aquasecurity/trivy, the operator’s docs at aquasecurity.github.io/trivy-operator, and the format specifications at cyclonedx.org and spdx.dev. Pair this page with Security & Policy Enforcement for the concepts, Sigstore & cosign for the signing half of the loop, Secrets Management for what to do when the secret scanner finds something real, The Tool Landscape for where scanning sits among everything else, and the glossary whenever a term stops making sense.
1. Name the four Trivy scanners and one finding each would produce. 2. Your Trivy step prints twelve CRITICAL findings and the pipeline goes green. What is missing? 3. Why does --ignore-unfixed usually make a gate more effective rather than less? 4. An image built and scanned clean in March is dangerous in June and nobody touched it. Explain, and say what you would deploy to catch it. 5. Name the two SBOM formats Trivy emits, and give a reason to scan an SBOM instead of an image. 6. Name three Trivy Operator report kinds and how you would list them. 7. Trivy says an image is clean. Is it safe to run? Why is that the wrong question?
Check your answers
- vuln — a CVE in an OS package or a lockfile dependency; misconfig — a Pod manifest with
privileged: trueor a Dockerfile running as root; secret — an AWS access key baked into a layer; license — a GPL-licensed transitive dependency in a proprietary product. --exit-code 1. Without it Trivy exits0no matter what it finds, so the report is informational wallpaper and nothing is blocked.- Because it removes findings nobody can act on — distribution will-not-fix CVEs with no fixed version — which is what causes teams to raise the severity threshold or delete the step entirely. A gate that fails only on actionable findings is a gate people keep. Report the unfixed ones in a separate non-blocking pass.
- The artifact is frozen but the vulnerability database is not: a scan is a join between the image and the world’s knowledge on the day you scanned. Deploy the Trivy Operator for continuous in-cluster re-scanning — or re-scan the stored SBOM on a schedule, which is cheaper and needs no registry pulls.
- CycloneDX and SPDX. Scan the SBOM because it is instant, needs no registry access or image pull, works offline, and lets you re-check hundreds of artifacts against a newly disclosed CVE in seconds.
VulnerabilityReport,ConfigAuditReport,ExposedSecretReport(alsoSbomReport,RbacAssessmentReport,InfraAssessmentReport,ClusterComplianceReport). List them withkubectl get vulnerabilityreports -A— they are ordinary namespaced objects owned by the workload that produced them.- “Clean” means no known vulnerability matched today’s database for the packages Trivy could identify — which is a much narrower claim. It says nothing about zero-days, about dependencies with no lockfile, about the image’s runtime behaviour, or about whether the image you are running is the one that was scanned. The better question is “is this the exact digest we scanned, signed and attested, and what is watching it now?” — answered by cosign, Kyverno and Falco, not by a scanner.