Learn Trivy: Scan Docker Images locally and Tailor Your Reports


Introduction
Every time we build a Docker image, we bundle together dozens of software components: base operating system, system libraries, language runtime, and our own application code. Each piece can have hidden security holes that attackers already know about. If we don’t catch those weaknesses early, they end up running in production and put our services (and our customers) at risk.
In this guide, we’ll solve that problem by using Trivy to scan our images local in our machine for known vulnerabilities – and then automate those scans in Gitlab CI/CD (In our upcoming blog) so we never ship unsafe containers by accident. By the end, we’ll have a separate process that:
Installs Trivy on our workstations or build server.
Scans images locally with clear CVE-based reports.
Generates an HTML report we can share or archive.
Prerequisites
Before we dive into scanning, make sure we have two things ready:
Container Runtime Installed (Docker/Podman)
Trivy Installed
Prerequisite-1: Docker/Podman Installed
Why: Trivy inspects container images – so we need a way to pull or build them locally.
How to Check(Docker): Below is the command to check the docker version. If this command fails, install Docker first.
Install Docker for macOS, Windows and Linux(Ubuntu/Debian): Docker Desktop: The #1 Containerization Tool for Developers | Docker
How to Check(Podman): Below is the command to check the podman version. If this command fails, install Podman first.
- Install Podman for macOS, Windows and Linux(all distros): Podman Installation | Podman
Tip: If we use Podman, just replace docker
with podman
in all commands. Trivy itself doesn’t care – trivy image …
command works whether our image came from Docker, Podman or any other OCI-compatible registry.
Prerequisite-2: Trivy Installed
Trivy is an open-source scanner from Aqua Security. For more info, visit Installation - Trivy). Some popular ways are given below:
Homebrew on macOS
brew install aquasecurity/trivy/trivy
trivy —version
APT on Ubuntu/Debian
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO -
https://aquasecurity.github.io/trivy-repo/deb/public.key
| sudo apt-key add - echo deb
https://aquasecurity.github.io/trivy-repo/deb
$(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
Running Trivy Locally
Before we plug-in Trivy into a pipeline, let’s see how it works on our local machine. We’ll learn how to
scan a Docker image in seconds (Basic Image Scan),
understand the CVE output (Understanding CVEs).
and pick up a few handy options that we can use all the time (Common Trivy CLI Options).
Basic Image Scan
build a local image
docker build -t rct-shared-workspace:latest . # if we have our own Dockerfile
Run Trivy: Trivy will download its latest vulnerability database (only the first time). It scans the OS packages, language libraries, and reports any known CVEs.
trivy image rct-shared-workspace:latest
Read the Results: We’ll see a table listing Library, Vulnerability, Installed Version, Fixed Version, and Severity (LOW, MEDIUM, HIGH, CRITICAL). If there are no issues, Trivy prints 'No vulnerabilities found.'
Understanding CVEs:
When we scan a container with Trivy, it checks every piece of software inside for known security problems called CVEs (Common Vulnerabilities and Exposures).
In the above output,
'CVE-2025-31498'
shows a known weakness inc-ares
library.Trivy downloads the latest CVE list, then looks at the exact versions of libraries and tools in our image.
If it finds a match, it informed us to update it with fixed version. Think of each CVE like a public 'trouble ticket' that describes a bug or weakness.
We can see some vulnerable CVEs of different libraries of our image with
Installed Version
andFixed Version
in the above image.
Note: When Trivy lists a CVE it also shows a Status, was shown in above output image:
Status | Fixed version | Meaning |
fixed | any (e.g., 1.34.5-r0 for c-ares library) | Our installed version already patched in the library-no action |
(Blank) | exists (e.g., 2.7.0-r0 for libexpat library) | Our Docker image is vulnerable-update Dockerfile by pulling the correct version |
(Blank) | (blank) (e.g., CVE 2025-24928 for libxml2 lib) | No patch yet-monitor the CVE until a fix drops. |
- Common Trivy CLI Options: Trivy’s CLI is straightforward. Below are a few flags we can use right away:
Option | Purpose |
--severity | We can show only vulnerabilities of certain levels. Example '--severity HIGH, CRITICAL '. |
--ignore-unfixed | We can skip CVEs that do not yet have a fix available. |
--timeout | We can set a max time for the scan. Example: '--timeout 5m '. |
--format | Choose output format: table (default), json , template . |
--template | Use a custom TPL file to shape HTML or other reports. (for more info: read 'Customizing Trivy Output' below) |
--exit-code <n> | Return a non-zero exit code when vulnerabilities are found (good for CI). |
--exit-code 0 | By default, Trivy always returns exit code '0' (success-no vuln found), even if it finds vulnerabilities. |
--exit-code 1 | '1' make Trivy return that code when any vuln is found. |
Tip: To see all options in one place, run trivy --help
Customizing Trivy Output
By default, Trivy shows us a nice table in our terminal – but sometimes we need structured data or a prettier report. Trivy supports many formats (incl. SBOM
, sarif
) but three main formats are:
TEXT
,JSON
, andHTML
FormatsTable/Text (default): A clean, color-coded table in our console.
trivy image rct-shared-workspace:latest
JSON: A machine-readable dump of everything Trivy finds. Great when we want to feed the results into other tools or scripts. We’ll get a big
JSON
object with fields likevulnerabilities
,Layer
,Target
, etc.trivy image rct-shared-workspace:latest --format json > report.json
HTML: A browser-friendly report, with collapsible sections and links. Perfect for sharing with our team:
trivy image rct-shared-workspace:latest --format template --output report.html
Tip: We can open report.html
in Chrome or Firefox to browse our vulnerabilities with below commands:
open report.html # macOS
sdg-open report.html # Linux (most distros)
start report.html # Windows (Powershell or Cmd)
Using a TPL Template (what is a '.tpl' ?)
A .tpl file is a small Go-style template that tells Trivy exactly how we want our custom report to look. Under the hood, Trivy takes our template, fills in the vulnerability data, and spits out HTML, Markdown, or whatever you define. We have two paths here:
Option 1 - Use the built-in HTML template: Trivy maintains a default HTML template in its Github repo. If we’re happy with the look and just want to generate a standard report, we can grab it directly. To see the default tpl template, visit https://github.com/aquasecurity/trivy/blob/main/contrib/html.tpl
# 1. Download the default template curl -sL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/html.tpl -o default-report.tpl # 2. Run Trivy with that template trivy image rct-shared-workspace:latest --format template --template @default-report.tpl --output report.html
Option 2 - Create your own template: If we need to tweak headers, colors, or include extra data, start from the default and adjust:
Copy the default (from above) into a new file, e.g.,
my-report.tpl
.Edit the Go-style template markers (
{{ … }}
) to add or remove the sections.Run Trivy pointing at our custom file:
trivy image rct-shared-workspace:latest --format template --template "@my-report.tpl" --output report.html
Wrap-up & What’s Next
We’ve now seen how to:
Install Docker and Trivy on our local machine.
Run Trivy locally to scan any Docker image in seconds.
Read the CVEs results and understand the
'fixed'
status.Customise Trivy output (
text
,JSON
,HTML
) and even use or tweak a .tpl template.
With those skills in place , we’re ready to automate our security checks in our CI/CD pipeline. In our upcoming blog, we’ll pick up right here and:
Wire up Trivy in Gitlab CI so every merge request triggers a vulnerability scan.
Use exit codes and severity filters to fail the build on critical findings.
Publish the HTML report as a pipeline artefact for easy review.
Walk through the full .gitlab-ci.yml step-by-step.
Stay tuned for the next one, where we’ll build a fully automated, “no-code-released-without-a-scan“ workflow in Gitlab.
Subscribe to my newsletter
Read articles from Abhinav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
