Trivy: Zero to Intermediate – Hands-on Tutorial

Course duration: 3 hours (180 minutes)
Audience: Absolute beginners, software developers, students, DevOps/DevSecOps engineers, SREs, and security practitioners
Reference Trivy version: v0.75.0, released October 1, 2026
Last documentation verification: October 11, 2026
Learning style: Approximately 40% concepts and 60% guided practice
Platforms: Windows 11 (PowerShell), macOS (Terminal), Ubuntu Linux (Bash)
Level on completion: Beginner to practical intermediate; not a substitute for a full application-security certification

Scope and accuracy note: Installation paths, command syntax, supported scanners, filtering, reporting, SBOMs, and GitHub Actions integration were cross-checked against Trivy’s official documentation as of the verification date. Scan output, finding counts, CVEs, fixed versions, and database-update timing change over time and are deliberately not invented here. The labs have not been executed in your environment. Follow the verification steps after each task. For organizational use, validate downloaded binaries and apply your normal software supply-chain controls.

Contents

  1. Course plan and lab prerequisites
  2. Module 1 — Introduction to Trivy (10 min)
  3. Module 2 — Security and vulnerability fundamentals (10 min)
  4. Module 3 — Architecture, installation, and setup (15 min)
  5. Module 4 — Container-image scanning (25 min)
  6. Module 5 — Filesystem and repository scanning (15 min)
  7. Module 6 — IaC misconfiguration scanning (20 min)
  8. Module 7 — Secret scanning (10 min)
  9. Module 8 — Filtering, triage, and remediation (20 min)
  10. Module 9 — Reports and SBOMs (15 min)
  11. Module 10 — CI/CD integration (15 min)
  12. Module 11 — Kubernetes cluster scanning (10 min)
  13. Module 12 — Capstone exercise (15 min)
  14. Troubleshooting and production checklist
  15. Quick command reference, quiz answers, and official sources

Course plan and lab prerequisites

What you will achieve

After the course, you can independently install Trivy; scan public and local container images; inspect code repositories and dependency lockfiles; detect Terraform, Dockerfile, and Kubernetes misconfigurations; scan for secrets; distinguish actionable vulnerabilities from accepted risks; produce machine-readable reports and software bills of materials; and enforce a simple CI security gate.

Timetable and lab deliverables

ModuleSubjectMinutesLearner’s evidence
1Introduction and use cases10State four classes of findings and two limitations
2CVE, CVSS, severity, packages10Explain one sample finding
3Architecture + installation15trivy --version and first successful scan
4Container images25Image scan and a locally built demo image (Docker optional)
5Filesystem + repositories15Source-tree and remote-repository scans
6IaC scanning20Scan intentionally insecure Terraform and Kubernetes YAML
7Secret detection10Scan a synthetic secret and inspect a finding
8Triage + remediation20Record, remediate, filter, and rescan one issue
9JSON, SARIF, and SBOM15Save three report formats
10CI/CD15Create and understand a GitHub Actions gate
11Kubernetes10Manifest scan or optional authorized cluster scan
12Final practical15Submit an evidence-based security report
Total180

Suggested path: This timetable is an instructor-led agenda. The optional Docker build, Windows/macOS alternatives, live Kubernetes access, extra exercises, and some download times may extend self-study beyond three hours. Skip optional work when following the strict schedule.

Requirements

ItemRequired?Why
Windows/macOS/Ubuntu laptop with a terminalYesExecute Trivy
Internet access to approved registries and Trivy databasesYes for initial scansPull images and update scanning databases
Permission to install a local CLIYesInstall Trivy and optional utilities
Text editor, e.g., VS CodeYesSave and edit practice files
GitRecommendedClone the safe teaching repository and commit CI config
Docker Engine / Docker DesktopOptional for image-building taskBuild an image locally; public-image scans can work without a local Docker daemon
jqOptional on Windows, recommended on macOS/LinuxExplore JSON reports; alternatives are given
GitHub repository with Actions enabledOptionalActually run the CI workflow; the YAML can be studied offline
kubectl plus permission to read a Kubernetes clusterOptionalLive-cluster demo only; YAML scanning requires no cluster
Terraform CLI, an AWS account, or cloud credentialsNot requiredTrivy performs static scanning on the local .tf files

Safety boundary: Practice only on sample files, public images, and systems you are authorized to scan. Never paste real credentials into a lab. Do not terraform apply insecure examples, expose a test cluster, or grant broad Kubernetes permissions just for a demonstration. Scanners do not prove an application is secure; they provide evidence to analyze.

Lab workspace and shell conventions

Use this workspace for all modules. Bash commands apply directly to macOS Terminal, Ubuntu, and Git Bash/WSL on Windows. PowerShell examples are called out explicitly. For Windows without Bash, create the same named files using your editor and run Trivy in PowerShell; the trivy commands themselves are shell-neutral.

# Bash — macOS/Ubuntu/Git Bash/WSL
mkdir -p "$HOME/trivy-workshop"/{app,terraform,kubernetes,secrets,reports}
cd "$HOME/trivy-workshop"
pwd
# PowerShell — Windows; no Administrator rights needed
$Lab = Join-Path $HOME 'trivy-workshop'
'app','terraform','kubernetes','secrets','reports' | ForEach-Object {
  New-Item -ItemType Directory -Force -Path (Join-Path $Lab $_) | Out-Null
}
Set-Location $Lab
Get-Location

Expected: an empty trivy-workshop folder containing app/, terraform/, kubernetes/, secrets/, and reports/. All subsequent commands assume you are inside trivy-workshop unless otherwise stated.


Module 1 — Introduction to Trivy (10 min)

Objectives

Define Trivy and DevSecOps; identify its principal scan targets and security findings; explain the difference between finding potential exposure and detecting an active attack; and know when to use each mode.

What is Trivy?

Trivy is an open-source security scanner maintained by Aqua Security and contributors. It inspects artifacts and configurations for known package vulnerabilities, security misconfigurations, exposed secrets, and license-related findings. Trivy can scan container images, filesystem directories, repositories, SBOMs, Kubernetes clusters, and other supported targets. Each target has different default scanners, so check --scanners rather than assuming every check runs automatically.

In DevSecOps, teams include security checks during development, commits, builds, deployments, and operations rather than waiting for a final audit. Shift left means bringing useful checks earlier in that flow. Trivy helps identify many preventable problems while developers can still change code, dependencies, or configuration easily.

Targets vs. findings: the key distinction

Target = what you inspectExample commandTypical question
Container imagetrivy image alpine:3.20Are installed OS or library packages vulnerable?
Local source treetrivy fs .Are dependency lockfiles or plaintext secrets problematic?
Remote source repositorytrivy repo https://github.com/aquasecurity/trivy-ci-testWhat known problems exist in the repository?
IaC and configurationtrivy config ./kubernetesAre manifest permissions or container security settings unsafe?
Generated SBOMtrivy sbom ./reports/sbom.jsonAre any inventoried dependencies known-vulnerable?
Kubernetes clustertrivy k8s --report summaryWhat issues are discoverable from authorized cluster access?

Finding types: vulnerability (vuln), misconfiguration (misconfig), secret (secret), and license (license). Some other Trivy capabilities are outside this course.

Trivy compared with other approaches

ApproachMain purposeTrivy relationship
Application log monitoringObserve what happened at runtimeComplementary: Trivy does not replace event monitoring
Antivirus/EDRDetect suspicious files or endpoint behaviorComplementary: Trivy is not a complete EDR/antivirus replacement
DAST or penetration testingExercise a running application’s exposed behaviorComplementary: Trivy does not prove an exploit works
Traditional CVE/SCA scannerMatch dependencies to known advisoriesOverlapping functionality; Trivy also handles images and configuration
Admission policy / runtime defenseStop or investigate workloads at runtimeComplementary: use policies and runtime tools alongside Trivy

What Trivy cannot guarantee

Trivy cannot promise to find zero-day vulnerabilities without applicable detection data, prove exploitability solely from a CVE record, reveal every business-logic flaw, inspect every dynamic runtime state, establish compliance by itself, or fix findings automatically. No output is not proof of no risk.

Mini exercise (2 minutes): Say which scan target you would choose for (a) an old Docker image, (b) an open-to-the-world SSH security group in Terraform, (c) a credential in a source file, and (d) a live Kubernetes cluster. Answers: image, config, fs/repo with secret, and k8s, respectively.

Checkpoint: Explain in your own words why a CVE scan and runtime intrusion detection are different.


Module 2 — Security and vulnerability fundamentals (10 min)

Objectives

Understand vulnerability, threat, risk, CVE, CWE, CVSS, advisory severity, fixed version, and the two major sources of vulnerable packages.

Core vocabulary

TermPlain-English definition
AssetSomething worth protecting: image, server, application, credential, or customer data
VulnerabilityA weakness that may be exploited
ThreatAn actor, event, or technique that could take advantage of a weakness
RiskLikelihood and business impact of an unwanted outcome, informed by exposure
CVECommon Vulnerabilities and Exposures identifier for a published issue, such as CVE-YYYY-NNNN
CWECommon Weakness Enumeration: category of underlying software weakness
CVSSCommon Vulnerability Scoring System: a structured severity scoring framework
IOCIndicator of compromise, for example evidence of a malicious file or network destination
SCASoftware Composition Analysis: identify third-party components and known vulnerabilities
OS packageOperating-system-managed dependency, e.g., openssl or libc
Language libraryApp dependency, e.g., a Python package or Node.js npm package
Fixed versionAn available vendor-supported version containing a relevant fix, if one exists

Severity and practical priority

Trivy reports UNKNOWN, LOW, MEDIUM, HIGH, and CRITICAL. A critical-looking result is important, but severity alone is not a complete remediation policy. Consider reachability, network exposure, privileges, business impact, exploit activity, availability of a fix, and compensating controls. Trivy uses ecosystem-specific vulnerability sources, and distro-maintained backports can mean package version comparisons are not as simple as upstream version ordering.

Hypothetical example — not a real detected CVE:

FieldExampleInterpretation
Finding IDCVE-20XX-12345Look up the advisory and the affected conditions
PackageexamplelibDependency containing the weak component
Installed version1.2.0Version currently inventoried
Fixed version1.2.9Candidate update, if compatible and vendor supported
SeverityHIGHStrong triage signal, not proof of exploitability

Why both operating-system and application scanning matter

A container image might bundle an Ubuntu/Debian base with OS packages and Python/Java/npm dependencies. Updating application dependencies without updating the base image leaves OS problems behind; replacing the base without updating lockfiles may leave library findings. A useful remediation plan addresses both.

Micro-quiz: (1) Is a CVE an attack? (2) Does a missing fixed version mean the finding is false? (3) Can a distro backport a fix without changing to the upstream major version? Answers: No; no; yes.


Module 3 — Architecture, installation, and setup (15 min)

Objectives

Understand how scanners run, install the CLI on your OS, verify its operation, and know what the vulnerability database and local cache do.

How a scan works

flowchart LR
    A[Target: image, filesystem, repo, config, Kubernetes] --> B[Trivy CLI]
    B --> C[Inventory packages and inspect files or configuration]
    C --> D[Select enabled scanners]
    E[Vulnerability and policy data / updates] --> D
    D --> F[Match vulnerabilities, misconfigs, secrets, licenses]
    F --> G[Apply severity and suppression filters]
    G --> H[Table, JSON, SARIF, SBOM / exit code]

Trivy analyzes the target, identifies relevant packages and files, consults vulnerability databases and built-in or downloaded security checks, then returns structured results. It does not need a persistent background service in normal standalone CLI mode. A client/server mode exists for specialized centralized deployments but is out of scope for this workshop.

Updates and caches: The first scan usually downloads database/check content and takes longer. Later scans can reuse local copies and refresh them as necessary. Network policy must permit the approved registry and database endpoints. Offline/air-gapped operation requires separate pre-provisioning and maintenance procedures; it is not achieved merely by adding --skip-db-update on an empty installation.

Quick Installation and Configuration

Choose one OS method below. No AWS/cloud login is needed. For Docker-based exercises, install Docker Desktop or Docker Engine separately from its official site; scanning public remote images does not automatically require Docker to be running.

Ubuntu Linux — official APT repository

Where: Ubuntu Terminal (Bash). Privileges: sudo for OS package installation only. Dependencies: wget, gnupg, CA certificates; git and jq aid later exercises.

# Step 1: prepare dependencies
sudo apt-get update
sudo apt-get install -y ca-certificates wget curl gnupg git jq

# Step 2: install the public signing key from the official Trivy repository
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \
  | gpg --dearmor \
  | sudo tee /usr/share/keyrings/trivy.gpg >/dev/null

# Step 3: register the official package repository
printf '%s\n' \
  'deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main' \
  | sudo tee /etc/apt/sources.list.d/trivy.list

# Step 4: install and verify
sudo apt-get update
sudo apt-get install -y trivy
trivy --version
command -v trivy

Expected: trivy --version shows a version number; the target for this tutorial is 0.75.0. The APT repository may provide newer versions later. To reproduce precisely, use the official v0.75.0 release package appropriate to your CPU architecture after validating the release checksums/signature. If your network blocks the repository, involve your package administrator rather than disabling TLS or signature checking.

Check: trivy image --scanners vuln alpine:3.20 should reach a public registry and complete an image scan. Zero or multiple findings are both possible; success means the command completes without a download/scan error.

Common problem: NO_PUBKEY/repository signature problem → confirm the key was downloaded from the intended host, keyring is readable, and source-list entry matches; do not disable GPG verification.

macOS — official Homebrew method

Where: macOS Terminal. Privileges: normal user with working Homebrew; do not use sudo brew. Dependencies: Homebrew installed from brew.sh via your organization-approved process; optional git, jq, and Docker Desktop.

# Step 1: confirm Homebrew exists
brew --version

# Step 2: install Trivy and convenient tutorial tools
brew update
brew install trivy git jq

# Step 3: verify the executable and version
trivy --version
command -v trivy

# Step 4: first public-image scan (no local Docker daemon required)
trivy image --scanners vuln alpine:3.20

Expected: Brew places trivy on the shell PATH and the image scan produces a report. On Apple Silicon, Homebrew commonly uses /opt/homebrew/bin; on Intel, /usr/local/bin. Avoid hardcoding either path.

Common problem: zsh: command not found: brew → install/initialize Homebrew using its own printed post-install instructions and open a new terminal. zsh: command not found: trivy → verify brew list trivy and brew --prefix and repair the Homebrew PATH.

Windows 11 — official release ZIP via PowerShell

Where: Windows PowerShell (not Bash), normal user. Privileges: Administrator is not required if installing under your own profile. Dependencies: network access to GitHub Releases; ZIP extraction is built in. The example is for 64-bit x86 Windows; use a release asset matching your hardware if different.

# Step 1: define the verified target version and per-user install location
$Version = '0.75.0'
$InstallDir = Join-Path $HOME 'Tools\Trivy'
$ZipPath = Join-Path $env:TEMP ('trivy_' + $Version + '_windows-64bit.zip')
$Url = 'https://github.com/aquasecurity/trivy/releases/download/v' +
       $Version + '/trivy_' + $Version + '_windows-64bit.zip'

# Step 2: download only from the official GitHub release
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
Invoke-WebRequest -Uri $Url -OutFile $ZipPath

# Step 3: unpack and check the downloaded executable
Expand-Archive -Path $ZipPath -DestinationPath $InstallDir -Force
& (Join-Path $InstallDir 'trivy.exe') --version

# Step 4: persist a per-user PATH entry if it is not present
$UserPath = [Environment]::GetEnvironmentVariable('Path','User')
if (($UserPath -split ';') -notcontains $InstallDir) {
    [Environment]::SetEnvironmentVariable(
        'Path', (($UserPath.TrimEnd(';') + ';' + $InstallDir).TrimStart(';')), 'User')
}
$env:Path += ';' + $InstallDir

# Step 5: run the first scan
trivy --version
trivy image --scanners vuln alpine:3.20

Expected: trivy.exe reports version 0.75.0; the scan completes and prints findings or a zero-finding summary. New PowerShell windows receive the updated user PATH. Windows Defender/organization download policies may require approval.

Common problem: ZIP returns HTTP 404 → visit official release assets and confirm exact asset name and CPU architecture. trivy not found → use & (Join-Path $InstallDir 'trivy.exe') --version, restart PowerShell, and verify PATH. Do not disable endpoint protections to force installation.

Optional containerized Trivy instead of host installation

On a Linux host with Docker, you can run the official image without installing the CLI globally:

# Linux Docker; public-image scan without mounting Docker socket
# First run may fetch the image and database. Cache is persisted to a user directory.
mkdir -p "$HOME/.cache/trivy-container"
docker run --rm \
  -v "$HOME/.cache/trivy-container:/root/.cache/" \
  aquasec/trivy:0.75.0 image --scanners vuln alpine:3.20

For scanning local daemon images, socket mounting gives containerized Trivy broad access to the host Docker engine; treat that as privileged access and use it only in a trusted lab. This option is supplemental, not a requirement.

CLI essentials and version check

trivy --help                 # List top-level targets and commands
trivy image --help           # Inspect flags for image scans
trivy fs --help              # Inspect filesystem-scan flags
trivy config --help          # Inspect IaC-only scanning options
trivy --version              # Check the installed CLI version

Typical syntax is trivy <target> [flags] <path-or-image>. --scanners chooses finding classes; --severity filters output by severity; --format changes the report format; --output writes a file; and --exit-code changes the exit status when qualifying findings exist. The defaults differ by scan target, so explicit scanner selection improves reproducibility.

Checkpoint: Why might the first scan take longer? What is the difference between a scan target and a scanner? Is a Docker daemon required for scanning a public image by registry access?


Module 4 — Container-image scanning (25 min)

Objectives and prerequisites

Learn the image-layer and package concepts, scan an old and current image, identify affected packages, filter by severity, distinguish --ignore-unfixed from remediation, and optionally build and scan your own container. Prerequisite: Module 3 installation and registry access. Docker is needed only for the local-image build.

What is inside an image?

A container image typically contains a base operating-system filesystem, installed system libraries, optional language packages, application files, and image metadata. Trivy primarily inventories known components and compares them with applicable advisories; a container image is not automatically safe because its app source code is small or its tag says latest.

flowchart TD
    A[Image: application + language packages + OS base] --> B[Package / metadata inventory]
    B --> C[OS-vulnerability advisory matching]
    B --> D[Application-library advisory matching]
    C --> E[Combined results]
    D --> E
    E --> F[Review CVEs, fix versions, exploit context]
    F --> G[Update base image or dependencies]
    G --> H[Rebuild and rescan]

Lab 4A — Scan a public image

Where: Terminal/PowerShell in trivy-workshop. Prerequisite: Trivy is on PATH; internet access. No Docker daemon required for this portion.

# 1. Basic image vulnerability scan
trivy image --scanners vuln nginx:1.20

# 2. Restrict displayed results to HIGH and CRITICAL
trivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20

# 3. Display findings for which the advisory identifies an available fix
trivy image --scanners vuln --ignore-unfixed nginx:1.20

# 4. Compare against a more current maintained image channel
trivy image --scanners vuln --severity HIGH,CRITICAL nginx:stable-alpine

Flags explained: image selects the image target; --scanners vuln makes the intent explicit; --severity HIGH,CRITICAL filters the visible severities; --ignore-unfixed hides findings without a reported fix. It does not patch software or establish that an unfixed issue is harmless.

Expected result: A report identifies the target and possibly one or more vulnerable packages with IDs, severities, installed versions, and fix-version information. Counts are intentionally unspecified because registry contents, OS support, advisory feeds, and the current DB change. If nginx:1.20 is no longer retrievable, choose another intentionally older public demo image from an authorized registry and record the actual tag.

Verification: Both scans complete without a registry/network error. Write down two package names (if findings exist), their severity, whether a fix is listed, and whether the issue is from the base OS or an application library.

Reading the report

Result columnWhat it meansAction
Library / package nameDetected affected software componentIdentify dependency owner
VulnerabilityCVE/advisory IDRead vendor advisory and affected conditions
SeverityAdvisory risk categoryPrioritize with real system context
Installed VersionComponent version in the artifactVerify artifact provenance
Fixed VersionPublished fixed candidate, if anyEvaluate update or rebuild
Status, where shownWhether fix exists or vendor accepts/deferred workDecide treatment and record exceptions

Avoid two beginner errors: (1) treating image tags as immutable; in real releases prefer image digests and traceable build IDs; and (2) equating zero displayed HIGH/CRITICAL findings with a secure image.

Lab 4B — Build a tiny local image (optional Docker track)

Where: trivy-workshop on macOS/Linux/Git Bash/WSL, or use a text editor on Windows. Prerequisite: Docker Engine/Desktop running. We create a very small HTTP server using Python standard library; no pip install is needed.

File 1 — app/server.py (entire file):

from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.end_headers()
        self.wfile.write(b"Trivy lab application is running\n")

if __name__ == "__main__":
    HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()

File 2 — Dockerfile (intentionally simplified learning configuration):

FROM python:3.11-slim
WORKDIR /opt/app
COPY app/server.py ./server.py
EXPOSE 8080
CMD ["python", "server.py"]

The example is intentionally missing an explicit non-root USER; this creates material to inspect and fix later. It is not a production Dockerfile.

Bash file creation (copy ready):

# In ~/trivy-workshop
mkdir -p app
cat > app/server.py <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.end_headers()
        self.wfile.write(b"Trivy lab application is running\n")

if __name__ == "__main__":
    HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
PY

cat > Dockerfile <<'DOCKERFILE'
FROM python:3.11-slim
WORKDIR /opt/app
COPY app/server.py ./server.py
EXPOSE 8080
CMD ["python", "server.py"]
DOCKERFILE

# Build, inspect, and scan

docker build -t trivy-workshop:insecure .
docker image inspect trivy-workshop:insecure --format '{{.Id}}'
trivy image --scanners vuln trivy-workshop:insecure
trivy config ./Dockerfile

Windows without Bash: Save the two shown file contents in trivy-workshop\app\server.py and trivy-workshop\Dockerfile using VS Code. In PowerShell from trivy-workshop, run the same docker build, docker image inspect, trivy image, and trivy config commands. The double braces in docker image inspect are Docker template syntax, not placeholders.

Expected: docker build exits successfully; docker image inspect prints an image identifier; trivy image analyzes the local image. The config scan may flag the lack of an explicit non-root user and other defaults depending on current policies.

Optional runtime verification:

# Docker publishes port 8080 on localhost only; Ctrl+C stops the foreground container.
docker run --rm -p 127.0.0.1:8080:8080 trivy-workshop:insecure
# In a second terminal:
curl http://127.0.0.1:8080/

PowerShell equivalent verification: Invoke-WebRequest http://127.0.0.1:8080/ | Select-Object -ExpandProperty Content.

Expected response: Trivy lab application is running. A successful runtime test does not prove the image is secure.

Lab 4C — Remediate and rescan

After observing image and Dockerfile results, replace Dockerfile with this improved baseline:

FROM python:3.11-slim
RUN useradd --create-home --uid 10001 appuser
WORKDIR /opt/app
COPY --chown=appuser:appuser app/server.py ./server.py
USER appuser
EXPOSE 8080
CMD ["python", "server.py"]
# Optional Docker track only
docker build -t trivy-workshop:nonroot .
trivy config ./Dockerfile
trivy image --scanners vuln trivy-workshop:nonroot

Verify: Dockerfile now explicitly declares a non-root user. Some misconfiguration results may disappear; dependency vulnerabilities may remain because both versions use the same base image. In production, also lock an approved base digest, rebuild regularly, test as non-root, and evaluate running with immutable/readonly filesystems where appropriate.

Common problems: Cannot connect to the Docker daemon → start Docker or skip the local build; UNAUTHORIZED/TOOMANYREQUESTS → authenticate to the allowed registry or try later; unable to fetch a tag → verify the tag exists; high findings with blank fixed version → plan mitigation, upstream replacement, or documented risk acceptance rather than inventing a patch.

Checkpoint: What is the functional difference between --ignore-unfixed and upgrading a package? Why could an updated Dockerfile still produce image CVEs?


Module 5 — Filesystem and repository scanning (15 min)

Objectives

Differentiate trivy fs and trivy repo; understand dependency manifests versus lockfiles; scan a folder and a remote repository; explicitly enable several scanner classes.

Filesystem vs. Git repository

  • trivy fs PATH inspects a local filesystem tree; good for checked-out projects and build directories. Its documented defaults include vulnerability and secret scanners.
  • trivy repo URL reads a local or remote Git repository, with repository-specific behavior and Git metadata; good for remote code inspection and CI. Its documented defaults also include vulnerability and secret scanners.
  • trivy config PATH targets configuration misconfiguration analysis directly; misconfiguration detection is not enabled by default for generic fs, repo, or image commands.

Dependencies may be declared in package.json, requirements.txt, pom.xml, and other manifests. Resolved dependency lockfiles, such as package-lock.json, Pipfile.lock, poetry.lock, or Gemfile.lock, help a scanner determine actual component versions. Trivy’s coverage varies by language, dependency ecosystem, and artifact type. Check its language coverage rather than assuming a manifest alone always yields a complete dependency inventory.

Lab 5A — Inspect your workspace

# Run in ~/trivy-workshop
trivy fs .

# Explicitly combine three scanner classes
trivy fs --scanners vuln,secret,misconfig .

# Restrict to vulnerability checks on the project tree
trivy fs --scanners vuln .

Expected: Trivy scans files present so far. If you created only server.py and Dockerfile, you may see few or no package vulnerabilities because there are no dependency lockfiles. When combining scanners, Terraform/Kubernetes/Dockerfile misconfiguration files from the next module will add more findings.

Lab 5B — Scan an official example repository

# Remote Git scan: no local clone necessary
trivy repo https://github.com/aquasecurity/trivy-ci-test

# Optional local path for inspecting manifests/lockfiles; requires Git
# Run this inside trivy-workshop. The repository is for learning only.
git clone https://github.com/aquasecurity/trivy-ci-test.git sample-repo
trivy fs --scanners vuln ./sample-repo

Expected: The repository scans successfully and may identify vulnerable dependencies in supported lockfiles. Record actual findings rather than fixed example totals. If a network rule prevents external Git cloning, scan a locally available, authorized sample repository instead.

Reading lockfile results

A lockfile can point to an exact or resolved dependency version. Trivy compares these versions to ecosystem advisories and may report both direct dependencies (chosen by a project) and transitive dependencies (pulled in by direct ones). Remediation is typically through a dependency-manager update and lockfile regeneration, not manual editing of lockfiles without testing.

Practical task: Inspect sample-repo for a lockfile, identify one library reported by Trivy, then explain which direct parent package or dependency update would need investigation. If no current vulnerability is returned, report that fact and identify a supported lockfile anyway.

Common problems: no results because no supported package metadata exists; remote repo access requires Git connectivity; private repositories need appropriate access tokens (do not print them); scanning only --scanners vuln intentionally excludes secrets and IaC issues.

Checkpoint: Why is trivy fs --scanners misconfig . different from trivy fs .? Why are lockfiles especially helpful in dependency scanning?


Module 6 — IaC misconfiguration scanning (20 min)

Objectives

Understand static IaC analysis; create insecure and safer Terraform and Kubernetes resources; scan a Dockerfile, Terraform, and Kubernetes YAML; interpret check IDs; fix a problem without deploying anything.

Infrastructure as Code (IaC) is the description of infrastructure in versioned configuration files, such as Terraform and Kubernetes YAML. A misconfiguration can be dangerous even when the application has no software CVEs. Examples include public SSH ingress, privileged containers, excessive IAM permissions, missing encryption, or missing workload restrictions. Static scanning checks what the file declares, not necessarily every runtime condition.

Lab 6A — Check Dockerfile policy

# The Dockerfile was created in Module 4
trivy config ./Dockerfile
trivy fs --scanners misconfig ./Dockerfile

Expected: Trivy evaluates built-in Dockerfile checks. Some checks depend on installed policy revisions and image metadata. If you already changed the Dockerfile to non-root, compare with the earlier example and document what improved.

Lab 6B — Terraform configuration, insecure on purpose

Where: save the following complete contents as terraform/main.tf. Do not run Terraform apply. You do not need an AWS account, an AWS SDK, or Terraform installed for trivy config.

# terraform/main.tf — LEARNING EXAMPLE: NEVER APPLY IN A REAL ACCOUNT
resource "aws_security_group" "open_ssh" {
  name        = "trivy-workshop-open-ssh"
  description = "Intentionally insecure SSH exposure for static scanning"

  ingress {
    description = "INSECURE: SSH exposed to the entire Internet"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Demo egress"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Bash create and scan:

cat > terraform/main.tf <<'TF'
resource "aws_security_group" "open_ssh" {
  name        = "trivy-workshop-open-ssh"
  description = "Intentionally insecure SSH exposure for static scanning"
  ingress {
    description = "INSECURE: SSH exposed to the entire Internet"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    description = "Demo egress"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
TF

trivy config ./terraform
trivy config --severity HIGH,CRITICAL ./terraform

Windows PowerShell scan: after saving the file in terraform\main.tf, run trivy config .\terraform.

Expected: A misconfiguration related to overly broad inbound SSH exposure should be considered by the applicable AWS security-group checks. Note the actual check ID, file path, line, severity, message, and suggested change. If the policy revision does not report the condition, inspect trivy config --help, policy/check update errors, and the file content; do not fabricate a specific check ID.

Fix: In this controlled learning file, change the ingress CIDR from 0.0.0.0/0 to 10.0.0.0/24, then rerun trivy config ./terraform. In a real system, restrict ingress to an explicitly authorized jump host, VPN, or security group; an RFC1918 range alone is not sufficient proof of secure access.

Lab 6C — Kubernetes YAML, intentionally insecure

Where: save as kubernetes/deployment-insecure.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: insecure-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: insecure-web
  template:
    metadata:
      labels:
        app: insecure-web
    spec:
      containers:
        - name: web
          image: nginx:1.20
          ports:
            - containerPort: 80
          securityContext:
            privileged: true
            allowPrivilegeEscalation: true

Bash create:

cat > kubernetes/deployment-insecure.yaml <<'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: insecure-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: insecure-web
  template:
    metadata:
      labels:
        app: insecure-web
    spec:
      containers:
        - name: web
          image: nginx:1.20
          ports:
            - containerPort: 80
          securityContext:
            privileged: true
            allowPrivilegeEscalation: true
YAML

trivy config ./kubernetes

Expected: Built-in Kubernetes policies may flag the privileged setting, privilege escalation, or other missing hardening properties. Policy severity and IDs can change. This file is not intended for deployment.

Lab 6D — Safer Kubernetes pattern

Save as kubernetes/deployment-secure.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: restricted-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: restricted-demo
  template:
    metadata:
      labels:
        app: restricted-demo
    spec:
      containers:
        - name: worker
          image: busybox:1.36.1
          command: ["sh", "-c", "sleep 3600"]
          securityContext:
            privileged: false
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            runAsUser: 10001
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
            seccompProfile:
              type: RuntimeDefault
          resources:
            requests:
              cpu: 25m
              memory: 32Mi
            limits:
              cpu: 100m
              memory: 64Mi

Scan and compare:

trivy config ./kubernetes/deployment-insecure.yaml
trivy config ./kubernetes/deployment-secure.yaml

# Try combined classes across the entire workshop (may take longer)
trivy fs --scanners vuln,misconfig,secret .

Expected: The restrictive manifest demonstrates least privilege, a non-root UID, seccomp, dropping capabilities, and resource limits. It may still have findings: scanners can recommend additional controls or discover unrelated issues. This is an illustrative configuration, not a fully reviewed production deployment.

Validation: Review the exact before/after findings and explain which settings address which risks. Optional kubectl --dry-run=client -f behavior varies by kubectl version and cluster schema availability; validation is not required for static Trivy scanning. Never deploy the insecure manifest just to make an alert appear.

Key practice and troubleshooting

  • Run trivy config PATH for IaC-only work; when combining with vulnerability or secret checks, use trivy fs --scanners vuln,misconfig,secret PATH.
  • Inspect --severity and --format json when table output feels incomplete.
  • Trivy policies/check bundles may update independently of the CLI; record the date of each scan.
  • If a file is skipped, confirm its extension is supported, check --skip-files/--skip-dirs, and verify the file parses correctly.
  • A passing IaC scan cannot substitute for reviewed identity/access policies, networking, cloud-native services, or runtime behavior.

Checkpoint: Is a Kubernetes manifest scan equivalent to scanning a running cluster? What is risky about privileged: true? Why should Terraform misconfiguration scanning happen before apply?


Module 7 — Secret scanning (10 min)

Objectives

Explain the risks of hardcoded credentials, execute a secret scan, understand false positives, safely configure a custom teaching rule, and know the correct response to a real leaked token.

Why it matters

An API token, cloud key, database password, or private key committed to a repository can be exposed through forks, build artifacts, or developer machines. Secret scanning finds certain credential patterns, not all secrets in every encoding or every location. A detected string might be a test example; a real leaked credential must be revoked or rotated, not merely deleted from the latest commit.

Lab 7A — Default secret scanner

# Scan the local workspace; avoids adding vulnerability noise
trivy fs --scanners secret .

# Scan a repository's secret patterns (authorized repositories only)
trivy repo --scanners secret https://github.com/aquasecurity/trivy-ci-test

Expected: A list of findings or no findings, depending on the files and built-in checks. Trivy has built-in detection patterns for common tokens and private keys, but 0 findings does not imply every credential is absent.

Lab 7B — Guaranteed training example with a custom rule

To avoid needing a real key, create a synthetic teaching pattern and an explicit custom rule. The following token is not a usable credential. It is intended to demonstrate a match only when our custom rule is enabled.

File secrets/demo.env (entire contents):

# Deliberately fake token for this workshop; not for application use
TRAINING_FAKE_API_TOKEN=DEMO_A1B2C3D4E5F6

File trivy-secret.yaml (entire contents):

rules:
  - id: tutorial-fake-api-token
    category: general
    title: Workshop fake API token
    severity: HIGH
    path: '.*\.env$'
    keywords:
      - TRAINING_FAKE_API_TOKEN
    regex: '(?P<secret>TRAINING_FAKE_API_TOKEN=DEMO_[A-Z0-9]{12})'
    secret-group-name: secret

Bash creation and scan:

cat > secrets/demo.env <<'ENVFILE'
# Deliberately fake token for this workshop; not for application use
TRAINING_FAKE_API_TOKEN=DEMO_A1B2C3D4E5F6
ENVFILE

cat > trivy-secret.yaml <<'SECRETCFG'
rules:
  - id: tutorial-fake-api-token
    category: general
    title: Workshop fake API token
    severity: HIGH
    path: '.*\.env$'
    keywords:
      - TRAINING_FAKE_API_TOKEN
    regex: '(?P<secret>TRAINING_FAKE_API_TOKEN=DEMO_[A-Z0-9]{12})'
    secret-group-name: secret
SECRETCFG

trivy fs --scanners secret \
  --secret-config ./trivy-secret.yaml ./secrets

Windows: use your editor to save secrets\demo.env and trivy-secret.yaml in the workspace root. Run:

trivy fs --scanners secret --secret-config .\trivy-secret.yaml .\secrets

Expected: A finding associated with tutorial-fake-api-token in secrets/demo.env, normally reported at HIGH, with the matching line. If absent, check that the YAML indents are spaces, path matches .env, file encoding is readable, and the --secret-config path is correct. Use trivy fs --help to verify the flag.

Verification: Point to the rule ID, severity, detected path, and example line. Explain why a custom regex is appropriate for a company-specific credential format but not proof that every secret is discovered.

Response to a real secret incident

  1. Identify the owner and scope without posting the full secret in a ticket or chat.
  2. Revoke/rotate the credential in its issuing service; investigate misuse and other copies.
  3. Remove it from code and artifacts; address repository history following your incident procedure.
  4. Move the credential to an approved secrets manager; narrow its permissions and lifetime.
  5. Add prevention checks and verify that new scans do not reproduce the leak.

Checkpoint: If a GitHub token was genuinely committed and then deleted, is the incident resolved? No: the token may persist in Git history and copies; rotate/revoke it first.


Module 8 — Filtering, triage, and remediation (20 min)

Objectives

Turn raw alerts into an actionable remediation plan; correctly use severity filtering, --ignore-unfixed, --exit-code, and .trivyignore; and verify improvement through re-scanning.

A practical triage framework

A security finding is an observation; a decision needs context. Work through these questions:

  1. Is the finding real and in scope? Check package names, affected versions, advisory conditions, and evidence.
  2. Is the vulnerable code or path exposed? Consider external connectivity, permissions, workload role, and active exploitation information.
  3. Is an upgrade available? Check fixed versions and vendor-supported package streams.
  4. What is the safest change? Upgrade a dependency, rebuild with a patched base, fix IaC, rotate a secret, or add a compensating control.
  5. Can we verify? Rebuild or rescan the actual released artifact, not only an unbuilt Dockerfile.
  6. If we accept risk, who approved it and for how long? Document owner, rationale, expiry, and follow-up.

Lab 8A — Severity filtering and build gates

# Scan without failing a shell script solely because findings exist (default behavior)
trivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20

# Exit status 1 if at least one selected issue is reported; 0 if no selected findings
trivy image --scanners vuln --severity HIGH,CRITICAL \
  --exit-code 1 nginx:1.20

# Bash: inspect status of the command immediately above
printf 'Trivy exit status: %s\n' "$?"

Windows PowerShell: after the scan, run Write-Host "Trivy exit status: $LASTEXITCODE".

Important distinction: --exit-code 1 changes behavior when qualifying findings exist. It is not a way to classify the root cause of every scan failure. Registry/network/parse errors also cause nonzero exit status, often with a different code. CI should report the scan error separately from vulnerability-policy failures.

Lab 8B — Filter vs. remediate

# Show only issues with available fixes (useful for upgrade planning)
trivy image --scanners vuln --severity HIGH,CRITICAL \
  --ignore-unfixed nginx:1.20

Why this matters: Hide/unhide operations change the report, not the artifact. A CVE that has no reported fixed version still deserves triage if it is exposed. For IaC settings, fix the corresponding configuration rather than searching for an OS package version.

Lab 8C — Record a finding and validate a fix

Create a remediation note (a plain Markdown file is sufficient) using this template:

## Finding record
- Target (image/tag/digest/file):
- Scanner type:
- Finding ID and actual severity:
- Package/check and exact affected location:
- Evidence and scan date:
- Reachability / business impact:
- Recommended action and owner:
- Planned deadline:
- Verification command and result:
- Exception approver / expiration, if needed:

Practice: Pick either the open-SSH Terraform finding from Module 6 or a fixable image vulnerability from Module 4. Apply one justified change, rescan, and record a before/after difference. For the Terraform example, editing cidr_blocks to an approved private range is the lab-level remediation; in real environments use documented network access requirements.

Lab 8D — Learn suppressions without hiding risk

The simple .trivyignore format accepts finding IDs and optional expiration annotations. Use this only for an explicitly approved exception, not to make CI green by default.

Illustration only — replace the ID with an actual finding from your scan:

# Sample syntax, not an actual acceptance decision:
CVE-2099-0001 exp:2026-11-30

If the finding ID is real and currently in the result, save your reviewed exception to .trivyignore in the directory where you run Trivy; then compare trivy image ... before and after. To avoid an accidental suppression during later exercises, delete the training-only .trivyignore after the demonstration. Trivy also supports an experimental structured .trivyignore.yaml form with extra fields; check the current stability warning before relying on it in production.

Never approve a blanket ignore of every HIGH/CRITICAL result. Prefer short-lived, scoped, ticket-linked exceptions with an owner and deadline.

Fix strategy cheat sheet

FindingTypical first treatmentVerification
Vulnerable OS packageRebuild from vendor-maintained, patched baseRescan newly built image digest
Vulnerable npm/Python/etc. dependencyUpgrade compatible direct dependency/lockfileRun unit tests, then rescan exact artifact
World-open SSH in TerraformRestrict allowed source or remove SSH accessScan updated .tf, review plan
Privileged Kubernetes containerRemove privilege, drop capabilities, use non-rootScan safe manifest and integration-test workload
Hardcoded tokenRevoke/rotate, remove, investigate exposureConfirm credential invalid and rescan repo
Apparently false-positive CVEValidate vendor advisory and affected configurationDocument evidence, expiry, and exception approval

Checkpoint: Which is better evidence: a severity-filtered green report or a rescan of a rebuilt fixed image? Why should every suppression have an owner and expiry?


Module 9 — Reports and SBOMs (15 min)

Objectives

Export table/JSON/SARIF results; understand an SBOM; generate CycloneDX and SPDX inventories; read a machine-readable result without relying on specific CVE totals.

Formats in plain language

FormatTypical consumerWhat it contains
TableHuman reading a terminal or logConvenient security findings
JSONScripts, dashboards, ingestion APIsStructured Trivy results and metadata
SARIFCode scanning integrationsStandardized static-analysis findings
CycloneDXSoftware supply-chain and inventory systemsSBOM package and component relationships; vulnerability inclusion depends on options
SPDX / SPDX JSONComponent/license inventory consumersMachine-readable software package inventory

An SBOM (Software Bill of Materials) answers “What software components are present?” It does not by itself prove that the application is vulnerable or safe. Trivy’s CycloneDX generation, by default, prioritizes inventory; to add vulnerability findings explicitly pass --scanners vuln where supported.

Lab 9A — Create reports

# Always run from the trivy-workshop root
mkdir -p reports

# 1. JSON vulnerability results
trivy image --scanners vuln --format json \
  --output reports/image-findings.json nginx:1.20

# 2. SARIF results for infrastructure-as-code
trivy config --format sarif \
  --output reports/iac-findings.sarif ./terraform

# 3. CycloneDX SBOM (component inventory)
trivy image --format cyclonedx \
  --output reports/image-sbom.cdx.json nginx:1.20

# 4. SPDX JSON SBOM (alternate standard)
trivy image --format spdx-json \
  --output reports/image-sbom.spdx.json nginx:1.20

# 5. Optional: scan an already generated SBOM for known vulnerabilities
trivy sbom --scanners vuln reports/image-sbom.cdx.json

PowerShell: create the reports folder first with New-Item -ItemType Directory -Path .\reports -Force | Out-Null, then run the same trivy commands without Bash line-continuation characters (\)—put each command on a single PowerShell line.

Expected: Four files exist under reports/. Contents vary by image digest and current database. The SBOM file should contain a component inventory even when vulnerability results are empty; the SARIF file should be a valid JSON-like structured report conforming to SARIF.

Verify with Bash:

ls -lh reports/
jq -r '.ArtifactName // .Metadata.ImageID // "Artifact metadata unavailable"' \
  reports/image-findings.json
jq -r '.bomFormat, .specVersion, (.components | length)' \
  reports/image-sbom.cdx.json

Windows PowerShell verification (no jq required):

Get-ChildItem .\reports
$Sbom = Get-Content .\reports\image-sbom.cdx.json -Raw | ConvertFrom-Json
$Sbom.bomFormat
$Sbom.specVersion
$Sbom.components.Count

Understand: the image-findings JSON usually contains Results entries; vulnerabilities may be absent in some entries. Never assume all fields are populated.

Lab 9B — Extract a basic triage table from JSON (optional jq)

jq -r '
  .Results[]? | .Target as $target |
  .Vulnerabilities[]? |
  select(.Severity == "HIGH" or .Severity == "CRITICAL") |
  [$target, .VulnerabilityID, .PkgName, .InstalledVersion,
   (.FixedVersion // "not listed"), .Severity] | @tsv
' reports/image-findings.json

Expected: Tab-separated finding rows if there are HIGH/CRITICAL vulnerabilities. An empty result simply means no entries met this query; check the unfiltered report before concluding there are no vulnerabilities at other severities.

Lab 9C — Include vulnerability records in CycloneDX deliberately

trivy image --scanners vuln --format cyclonedx \
  --output reports/image-sbom-with-vulns.cdx.json nginx:1.20

Why: A software inventory and a vulnerability report serve related but distinct purposes. Ensure downstream consumers understand which data your SBOM contains.

Reporting security considerations

Trivy reports can include file paths, dependency metadata, and secret-scan details. Treat CI artifacts as potentially sensitive; restrict repository/report access, choose a retention period, avoid publishing raw leaked credentials, and review third-party ingestion destinations.

Checkpoint: Which output format fits GitHub code-scanning ingestion? Which formats represent package inventories? Why could an SBOM contain components but no vulnerability objects?


Module 10 — CI/CD integration (15 min)

Objectives

Understand a simple security gate, add a Trivy GitHub Actions workflow, explain its failure modes, and distinguish build failure from a completed scan with findings. GitHub access is only needed to run the workflow; writing the YAML is a local exercise.

How CI security gates work

A CI/CD pipeline can call Trivy automatically when code changes. A report answers what was found; a security gate determines whether the pipeline should continue according to an agreed policy. Teams may choose advisory-only checks first and progressively enforce approved severity thresholds. For mature pipelines, consider scan coverage, exception governance, SBOM retention, and false-positive management.

flowchart LR
    A[Developer commit / pull request] --> B[CI checks out code]
    B --> C[Trivy scans filesystem, dependencies, IaC and secrets]
    C --> D{Issues match policy?}
    D -->|No| E[CI security gate passes]
    D -->|Yes| F[CI fails and publishes findings]
    F --> G[Remediate or approve time-bound exception]
    G --> A

Lab 10A — Write a GitHub Actions workflow

Where: trivy-workshop/.github/workflows/trivy-security.yml. Prerequisites: local editor; an actual GitHub repository is optional. The aquasecurity/trivy-action usage shown below is based on the official action v0.36.0 interface and installs Trivy v0.75.0 for the run.

Complete file contents:

name: Trivy Security Check

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  security-scan:
    name: Scan source and IaC
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Trivy filesystem security gate
        uses: aquasecurity/trivy-action@v0.36.0
        with:
          scan-type: fs
          scan-ref: '.'
          scanners: 'vuln,secret,misconfig'
          format: table
          severity: 'HIGH,CRITICAL'
          exit-code: '1'
          version: 'v0.75.0'

Bash creation (copy ready):

mkdir -p .github/workflows
cat > .github/workflows/trivy-security.yml <<'WORKFLOW'
name: Trivy Security Check
on:
  push:
    branches: [main]
  pull_request:
permissions:
  contents: read
jobs:
  security-scan:
    name: Scan source and IaC
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout source
        uses: actions/checkout@v4
      - name: Trivy filesystem security gate
        uses: aquasecurity/trivy-action@v0.36.0
        with:
          scan-type: fs
          scan-ref: '.'
          scanners: 'vuln,secret,misconfig'
          format: table
          severity: 'HIGH,CRITICAL'
          exit-code: '1'
          version: 'v0.75.0'
WORKFLOW

Windows: create .github\workflows\trivy-security.yml using your editor, copying the full YAML above. GitHub Actions YAML syntax is the same on Windows/macOS/Linux.

Explain the important workflow options

ItemMeaning
pull_requestRun when a PR is opened or updated
push on mainScan the main branch when it changes
permissions: contents: readKeep default token privileges low
actions/checkout@v4Give the job a copy of source files
scan-type: fsScan the checked-out filesystem, not an already-built image
scannersEnable vulnerabilities, secrets, and misconfiguration checks explicitly
severityEnforce only the severity bands listed
exit-code: '1'Fail the step if matching findings exist
versionChoose the CLI version; independent of the action version

Expected: On a GitHub repository with Actions enabled, the job checks out code and runs Trivy. If you commit the intentionally insecure Terraform or Kubernetes example, a matching HIGH/CRITICAL IaC result may fail the gate. This is intentional lab behavior. A scan error (bad YAML, blocked DB download, inaccessible repository) is a different cause of failure and must be diagnosed separately.

Verify locally first:

trivy fs --scanners vuln,secret,misconfig \
  --severity HIGH,CRITICAL --exit-code 1 .
# Bash: inspect result immediately afterwards
printf 'Status: %s\n' "$?"

GitHub workflow validation: Commit the YAML into a test repository you control, open the GitHub Actions tab, inspect the exact Trivy step, read its output, fix one finding, and compare a new run. GitHub account/repository setup and pushing are separate prerequisites; never automatically upload the lab to a company repository.

Keep lab reports and fake secrets out of Git

Before committing, create a .gitignore in the workshop root:

# Local outputs and deliberately synthetic practice files
reports/
secrets/
.trivyignore

# Generic generated or local-only items
*.log
.env

This exclusion does not make a genuine leaked credential safe and is not a replacement for secret scanning. It simply avoids accidentally publishing practice outputs. If you want the CI demonstration to fail on the insecure Terraform rule, commit only the synthetic IaC lab file, not a real secret.

Production hardening: action pinning and reporting

The above @v0.36.0 and @v4 tags are convenient for a teaching example. For a production pipeline, resolve and pin every third-party GitHub Action to its verified, full commit SHA using your supply-chain review process. Do not invent a SHA or copy one from an unrelated release. Restrict workflow permissions and avoid executing unreviewed privileged workflow code from untrusted PRs.

Optional SARIF pattern: Run a separate reporting step with format: 'sarif', output: 'trivy-results.sarif', and exit-code: '0', then use github/codeql-action/upload-sarif with permissions: security-events: write (where GitHub code scanning is supported). A separate gating step can still fail on qualifying issues. Avoid uploading raw secret contents to publicly visible artifacts.

CI/CD outside GitHub: GitLab CI and Jenkins execute the same CLI with their own runners and artifact storage. The key portable command is trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 ..

Checkpoint: What is the difference between exit-code: '0' for report collection and exit-code: '1' for a gate? Why should third-party workflow dependencies be commit-SHA pinned in production?


Module 11 — Kubernetes cluster scanning (10 min)

Objectives

Distinguish local YAML from live-cluster scanning; understand trivy k8s, common RBAC requirements, the node collector, namespace scoping, and the purpose of Trivy Operator.

Manifest vs. cluster: not the same scan

trivy config kubernetes/ analyzes your saved Kubernetes YAML; it requires no cluster and has no insight into arbitrary settings applied to a running deployment by other controllers. trivy k8s uses Kubernetes API access to analyze cluster resources and associated images and can inspect broader posture. As of this document, the official guide labels Kubernetes-target scanning experimental. Some scans may download many images and, by default, the node collector may create Kubernetes Jobs. Do not use a production cluster casually.

Lab 11A — Everyone can scan manifests

trivy config ./kubernetes/deployment-insecure.yaml
trivy config ./kubernetes/deployment-secure.yaml

Expected: Configuration findings (or none) for each YAML. Compare the security contexts and any check IDs. This is the main 10-minute course path if you have no cluster.

Lab 11B — Optional read-focused cluster demonstration

Prerequisites: kubectl installed, a kubeconfig for a non-production cluster you are authorized to inspect, organization-approved permission to list resources, and an understanding of the cluster’s cost and network policy. Confirm the current context before running any cluster scan.

# Review before proceeding
kubectl config current-context
kubectl auth can-i list deployments --all-namespaces
kubectl auth can-i list pods --all-namespaces

# If authorized: scope to default namespace, skip image downloads,
# and disable node-collector Jobs to reduce impact.
trivy k8s --scanners misconfig --report summary \
  --include-namespaces default \
  --skip-images \
  --disable-node-collector

Expected: A summary of misconfiguration results for resources Trivy can discover under your credentials. --skip-images omits image vulnerability/secret analysis, and --disable-node-collector omits node-hardening checks, so this is not a comprehensive cluster audit. RBAC denied errors mean your role lacks necessary reads; do not grant cluster-admin merely to complete a workshop.

Fuller scan, only with explicit approval:

# May download many images and create node-collector Jobs by default.
# Read the official role/resource-impact guidance before running.
trivy k8s --report summary

Trivy Operator is a separate deployment that schedules recurring security work inside Kubernetes and exposes results over time. Installing the Operator and configuring metrics, storage, or its RBAC are advanced topics and are outside the 3-hour workshop.

Checkpoint: Why can trivy k8s demand Kubernetes permissions that trivy config does not? What security coverage is omitted by --skip-images and --disable-node-collector?


Module 12 — Capstone exercise (15 min)

Scenario

You are a junior DevSecOps engineer asked to assess a small application before release. It has a container image, a Dockerfile, a Terraform security group, Kubernetes YAML, and an example secret. Your task is to identify real observations, prioritize them, fix one problem, preserve evidence, and suggest a simple CI gate.

Fifteen-minute guided task

Minutes 0–3 — Inventory the workspace

# Bash: files present in workshop (Windows: Get-ChildItem -Recurse)
find . -maxdepth 3 -type f | sort
trivy --version

Write down which files are source, configurations, and reports. Confirm your Trivy version.

Minutes 3–7 — Perform three different scans

trivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20
trivy config ./terraform
trivy config ./kubernetes/deployment-insecure.yaml

Then run the safe custom secret demo if time permits:

trivy fs --scanners secret --secret-config ./trivy-secret.yaml ./secrets

Minutes 7–10 — Fix one meaningful finding

Change the Terraform security-group CIDR from 0.0.0.0/0 to the approved lab-restricted 10.0.0.0/24, or improve the Dockerfile security context, or change the Kubernetes manifest’s privileged setting and privilege escalation. Re-scan the exact edited file and record what changed. If a scanner did not flag the issue, still explain the insecure behavior rather than claiming a finding it did not report.

Minutes 10–13 — Export evidence

mkdir -p reports
trivy image --scanners vuln --format json \
  --output reports/capstone-image.json nginx:1.20
trivy config --format sarif \
  --output reports/capstone-iac.sarif ./terraform
trivy image --format cyclonedx \
  --output reports/capstone-sbom.json nginx:1.20

PowerShell: run each Trivy line as a single command and create reports using New-Item -ItemType Directory -Force reports.

Minutes 13–15 — Prepare a short release recommendation

Complete this report in plain English (replace brackets with your actual observations):

# Trivy security assessment — workshop

- Scan date and CLI version: [actual]
- Image and repository targets: [actual]
- Vulnerability observations: [actual counts/IDs, or none]
- IaC findings: [actual check IDs, files, severity, or none]
- Secret demonstration: [detected/not detected, custom rule used]
- Top priority and why: [severity + exposure + impact]
- Action taken: [exact before/after change]
- Verification: [exact command and actual result]
- Remaining risk or blockers: [actual]
- CI gate recommendation: [scanners, severities, exception handling]

Passing criteria

  • [ ] Correctly choose at least three scan targets and their flags.
  • [ ] Explain the difference between package CVEs and IaC misconfigurations.
  • [ ] Record one actual result without inventing counts or IDs.
  • [ ] Make a security improvement and demonstrate a rescan.
  • [ ] Create a JSON report, SARIF file, and CycloneDX SBOM.
  • [ ] Explain what the CI policy should fail on and what it should merely report.
  • [ ] State what remains unknown after Trivy scanning.

Stretch challenge (outside 3 hours): Add a separate image-scan job for a locally built application image, record the image digest, pin Actions by full SHA, and automate publishing a signed SBOM according to company policy.


Troubleshooting and production checklist

Troubleshooting table

Read the symptom, identify likely cause, perform diagnosis, make the safest corrective change, and verify. Avoid running commands that discard files or change cluster state unless explicitly authorized.

SymptomLikely causeDiagnoseSafe fix and verification
trivy: command not found / trivy is not recognizedCLI absent or outside PATHtrivy --version; Bash: command -v trivy; PowerShell: Get-Command trivyInstall via official method, fix PATH, reopen terminal; rerun trivy --version
Ubuntu APT key or signed repository errorKeyring not installed, key/network mismatchInspect /etc/apt/sources.list.d/trivy.list, keyring path, and exact errorRe-follow official signed repository setup; never bypass repository signature checks; verify apt-cache policy trivy
macOS brew/Trivy not foundHomebrew shell initialization missingbrew --version, brew --prefixFollow Homebrew’s shell-environment output and reopen Terminal; verify trivy --version
Windows download says 404Asset name or CPU architecture mismatchCheck official release assets and $UrlDownload correct signed/checksummed release matching architecture; verify executable version
failed to download vulnerability DBProxy, DNS, TLS, policy, rate limit, or no networkRe-run with --debug; check DNS and allowed registry endpointsRequest correct proxy/allowlist or retry; do not disable TLS verification; verify image scan succeeds
TOOMANYREQUESTS / registry limitPublic registry pulls exceeded quotaCheck registry response and rate-limit noticeAuthenticate to approved registry or use approved mirror/cache; rerun scan
Cannot connect to the Docker daemonDocker stopped, socket unavailable, permission denieddocker infoStart/configure Docker; use remote public-image scan or approved local socket access; verify docker info
Trivy reports no dependency findingsNo supported package metadata, scanner disabled, or genuinely no known CVEstrivy fs --scanners vuln .; inspect lockfilesScan supported lockfiles/image artifacts; update database; do not assert zero risk from zero findings
A Dockerfile/Terraform check does not appearMisconfiguration scanner not enabled or file skippedtrivy config PATH; inspect file and parse errorsSelect config or --scanners misconfig; verify file extension and path
Synthetic secret is not detectedCustom rule not loaded, regex or path mismatchCheck trivy-secret.yaml and secrets/demo.env; use --secret-configCorrect indentation/path/group and rerun secret-only scan
unknown flagCLI too old/new, mistyped flag, wrong commandtrivy --version; trivy <target> --helpUse CLI-specific documented flags or install target release; verify --help
Scan takes too longFirst DB download, large image/repo, many filesNote target size, logs, timeoutNarrow scope, skip unrelated directories or use an approved cache; check again without silently dropping important scanners
GitHub Actions fails immediatelyInvalid YAML/action version, DB download error, missing repo permissionsOpen the failing step’s logResolve syntax or access cause; do not suppress the failure; rerun on a PR
GitHub Actions fails with findingsexit-code: '1' and policy matchedInspect Trivy output and exact finding IDsRemediate or approved time-bound exception; rerun and confirm outcome
Kubernetes Forbidden / RBAC deniedRead permissions absent or node collector needs extra rightskubectl auth can-i ... and review official required rolesUse approved least-privilege role; do not assign cluster-admin as a shortcut; rerun scoped scan
No Kubernetes contextkubectl/kubeconfig missingkubectl config current-contextObtain authorized context; use trivy config on manifests meanwhile
YAML parsing errorIndentation or invalid YAML syntaxOpen file in editor with YAML validation; inspect actual lineFix syntax and rerun trivy config PATH
jq: command not foundOptional tool missingjq --versionInstall jq from a trusted package manager, or use PowerShell ConvertFrom-Json

Debugging commands: non-destructive

trivy --version
trivy image --help
trivy config --help
trivy --debug image --scanners vuln alpine:3.20

--debug provides diagnostic logs; redact tokens, private URLs, and sensitive paths before sharing logs externally. Avoid trivy clean or deleting the cache casually; removing cached databases requires downloading them again and can break scanning in restricted networks.

Production readiness: minimum controls

A useful Trivy implementation is more than a single green pipeline:

  • [ ] Coverage policy: identify target types (image, dependency/lockfile, IaC, secret, Kubernetes) and when each is scanned.
  • [ ] Reproducibility: record CLI/action version, vulnerability DB freshness, image digest, commit SHA, and timestamp for evidence.
  • [ ] Action integrity: verify release checksum/signature where available; pin third-party GitHub Actions to verified full commit SHAs in production.
  • [ ] Least privilege: keep registry tokens, GitHub tokens, and cluster service accounts read-only where possible; isolate runners.
  • [ ] Credential protection: store secrets in a proper manager; restrict report/artifact access; rotate exposed keys immediately.
  • [ ] Triage ownership: vulnerability remediation has a responsible owner, priority, deadline, and evidence of testing.
  • [ ] Exceptions: require approval, rationale, scope, and expiration; regularly review all ignored findings.
  • [ ] Coverage limits: supplement Trivy with SAST, tests, runtime detection, penetration testing, cloud security posture management, and dependency maintenance.
  • [ ] CI reliability: distinguish scan-service failures from policy violations and avoid untracked silencing of checks.
  • [ ] Kubernetes caution: understand permission requirements, possible node-collector Jobs, registry downloads, costs, and production change-control.
  • [ ] SBOM lifecycle: store SBOMs with the corresponding release artifact and restrict access according to organizational policy.
  • [ ] Continuous improvement: measure newly introduced findings, age of unresolved issues, false positives, and repeat vulnerabilities over time.

From intermediate to advanced: next-step topics

The following are deliberately outside the three-hour core and make good follow-up workshops:

  1. Writing and testing custom misconfiguration checks with Rego/OPA.
  2. Evaluating VEX statements, reachability, and exploit context instead of severity alone.
  3. Automating SBOM generation, attestation, signing, and provenance verification.
  4. Private registries, authentication, registry mirrors, cache and database distribution, and air-gapped environments.
  5. Enterprise exception policy, policy-as-code, scan baselines, and multi-team reporting.
  6. Deploying and managing Trivy Operator in Kubernetes with secure RBAC and metrics.
  7. Running concurrent scans at scale and optimizing CI pipelines, performance, and data freshness.
  8. Using Trivy alongside runtime security tools and cloud security posture programs.

Quick command reference, quiz answers, and official sources

Copy-ready command reference

# Installation check
trivy --version

# Container images
trivy image nginx:1.20
trivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20
trivy image --scanners vuln --ignore-unfixed nginx:1.20

# Source code and repositories
trivy fs .
trivy fs --scanners vuln,secret,misconfig .
trivy repo https://github.com/aquasecurity/trivy-ci-test

# IaC-only checks
trivy config ./Dockerfile
trivy config ./terraform
trivy config ./kubernetes

# Secrets
trivy fs --scanners secret --secret-config ./trivy-secret.yaml ./secrets

# Reports, software inventory, and scan gates
trivy image --scanners vuln --format json --output reports/image.json nginx:1.20
trivy config --format sarif --output reports/iac.sarif ./terraform
trivy image --format cyclonedx --output reports/sbom.json nginx:1.20
trivy image --format spdx-json --output reports/sbom.spdx.json nginx:1.20
trivy image --scanners vuln --severity HIGH,CRITICAL --exit-code 1 nginx:1.20

# Kubernetes: requires approved access; disable node collector in casual demos
trivy k8s --scanners misconfig --report summary \
  --include-namespaces default --skip-images --disable-node-collector

Knowledge checks: suggested answers

ModuleCheckpoint answer
1A CVE scan finds known vulnerable components or misconfigurations; runtime intrusion tools observe or respond to behavior, which Trivy does not fully cover.
2A CVE identifies a published issue, not an attack. Missing fixed versions do not make a CVE false; OS maintainers may backport fixes.
3The first scan often downloads vulnerability/check data. Target is the artifact; scanner is the type of issue checked. Public-registry image scans do not inherently require a local Docker daemon.
4--ignore-unfixed only filters findings; it changes no packages. Dockerfile hardening does not necessarily update vulnerable OS or language libraries.
5Default fs scanning includes vulnerability and secret checks; misconfig needs explicit selection. Lockfiles reveal resolved versions.
6A YAML scan inspects source configuration; cluster scanning inspects discovered live resources. Privileged containers can bypass isolation. Pre-deploy IaC scanning catches issues before they become infrastructure.
7Deleting an exposed token from a current commit is insufficient; revoke/rotate it and investigate history/copies.
8A rebuilt-and-rescanned artifact provides better fix evidence than filtered output. Exceptions need owner and expiry so suppressed risk is reassessed.
9SARIF supports code-scanning ingestion; CycloneDX and SPDX represent SBOMs. Component inventories can exist without vulnerability data.
10Report-only exit-code: 0 allows collection; exit-code: 1 enforces a gate. Full commit-SHA pinning limits unexpected third-party workflow changes.
11k8s needs Kubernetes API permission and may do cluster-impacting work; static config does not. Skipping images and the node collector omits image findings and node hardening assessment.
12A good report records actual findings, a justified fix, proof from rescanning, a clear CI recommendation, and unknown residual risks.

Official documentation and last-verified references

All links below are to upstream vendor/project or relevant standard documentation. Last checked: October 11, 2026. Some pages show examples using older images by design; always check your installed release’s --help output.

  1. Trivy official documentation (latest) — introduction, targets, supported scanners.
  2. Trivy v0.75.0 GitHub release — release identity, assets, and verification materials; released October 1, 2026.
  3. Official installation methods — Ubuntu, macOS, Windows, binary, container.
  4. Container image scanning — image lookup, layers, Docker/registry scanning.
  5. Filesystem scanning — local project scanning and defaults.
  6. Code repository scanning — local/remote Git repositories.
  7. Misconfiguration scanning — IaC, built-in checks, scanner selection.
  8. Secret scanning — built-in detections, custom regex rules, secret configuration.
  9. Result filtering and suppression — severity, status, .trivyignore, exceptions.
  10. Reporting formats — JSON, SARIF, templates.
  11. SBOM generation and scanning — CycloneDX, SPDX, SBOM targets.
  12. Kubernetes scanning — live scanning, required roles, node collector, experimental-status warning.
  13. Official Trivy GitHub Action and action releases — YAML inputs and action versions.
  14. Trivy Operator project — continuous Kubernetes security scans (advanced).
  15. CVE Program, CWE, and FIRST CVSS — background terminology.

Final takeaway

Scan → understand → prioritize → remediate → verify → automate is the practical Trivy workflow. Focus on demonstrating these six activities rather than memorizing option lists. If you can inspect a container image, a dependency inventory, a Terraform or Kubernetes file, and a CI report—and can explain what Trivy did not check—you have a solid practical beginner-to-intermediate foundation.

End of tutorial — Trivy Zero to Intermediate, 3-hour training, reference version v0.75.0.

Related Posts

Terraform Lab – Azure –  Local Values and Outputs

Continuation of Labs 1–4 and Part 2 (All Six Input Variable Types)Level: Beginner → Early IntermediateTerraform: 1.15.9+ within version 1.xAzureRM provider: 5.9.x (same provider constraint as Part 2)Date: 12 October 2026…

Read More

Terraform Lab – Azure – Input Variables – VMS & Netowork Demo

Continuation of Labs 1–4 · Level: Beginner · Terraform: 1.15.9 · AzureRM: 5.9.x · Updated: 12 October 2026 Objective Terraform Lab – Azure – Resources – VMS & Netowork Demo Convert the final, consolidated Lab 4 infrastructure to…

Read More

Terraform Lab – Azure – Resources – VMS & Netowork Demo

Level: Absolute BeginnerTerraform version: 1.15.9AzureRM Provider: 5.9.0Approach: Each lab builds on the previous lab. These are four complete Terraform configurations designed for students to execute sequentially. Learning Path 1. Selected Azure…

Read More

Azure: Terraform Azure Authentication: Azure CLI

Latest Terraform Azure Authentication: Azure CLI The recommended approach in the HashiCorp AzureRM provider’s Azure CLI authentication guide is to authenticate through Azure CLI when running Terraform…

Read More

Azure: Authenticating to Azure using a Service Principal and a Client Secret

Azure Portal: Get all 4 Terraform authentication values You can do this entirely through the Azure Portal web interface. No terminal commands or Azure CLI are required….

Read More

Azure: How to Create an Azure User, Assign Permissions, and Log In

Objective Create a new user in Microsoft Azure, grant access to cloud resources, and verify that the user can log in independently without sharing your administrator credentials….

Read More