Learn Trivy: Scan Docker Images locally and Tailor Your Reports

AbhinavAbhinav
7 min read

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:

  1. Installs Trivy on our workstations or build server.

  2. Scans images locally with clear CVE-based reports.

  3. 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.

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:

  1. Homebrew on macOS

    brew install aquasecurity/trivy/trivy

trivy —version

  1. APT on Ubuntu/Debian

    1. sudo apt-get install wget apt-transport-https gnupg lsb-release

    2. 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

    3. sudo apt-get update

    4. 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

  1. scan a Docker image in seconds (Basic Image Scan),

  2. understand the CVE output (Understanding CVEs).

  3. 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 in c-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 and Fixed Version in the above image.

Note: When Trivy lists a CVE it also shows a Status, was shown in above output image:

StatusFixed versionMeaning
fixedany (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:
OptionPurpose
--severityWe can show only vulnerabilities of certain levels. Example '--severity HIGH, CRITICAL'.
--ignore-unfixedWe can skip CVEs that do not yet have a fix available.
--timeoutWe can set a max time for the scan. Example: '--timeout 5m'.
--formatChoose output format: table (default), json, template.
--templateUse 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 0By 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:

  1. TEXT, JSON, and HTML Formats

    • Table/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 like vulnerabilities, 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)
  1. 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:

    1. 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
      

    2. 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:

  1. Install Docker and Trivy on our local machine.

  2. Run Trivy locally to scan any Docker image in seconds.

  3. Read the CVEs results and understand the 'fixed' status.

  4. 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.

0
Subscribe to my newsletter

Read articles from Abhinav directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abhinav
Abhinav