Your homelab runs dozens of Docker containers. Each image pulls in layers from upstream registries — Alpine, Debian, nginx, Python, PostgreSQL. And every layer can ship with known vulnerabilities that get discovered months after you pulled it.

Manual audits don’t scale. You need an automated scanner that checks every running image against the latest CVE databases, flags misconfigurations, detects embedded secrets, and generates reports you can actually act on.

Trivy from Aqua Security does all of this. It is fast, dependency-free (one binary or a container), covers OS packages and language-specific libraries, and integrates cleanly into a homelab automation workflow. This guide walks through installation, scanning workflows, output formats, and a complete systemd timer setup for weekly unattended scans.


Installing Trivy

Trivy ships as a single static binary, a Docker image, and packages for every major distro. For a Proxmox or bare-metal Linux host, install the binary directly — it avoids the overhead of scanning images from inside a container.

Debian/Ubuntu Repository Install

1
2
3
4
5
6
7
# Add Aqua Security repository
wget -qO- https://aquasecurity.github.io/trivy-repo/deb/public.key | \
  gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "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
sudo apt update && sudo apt install trivy -y

Docker-based Usage

1
2
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image <image-name>

Verify Installation

1
2
3
4
5
6
trivy --version
# Should output something like:
# Version: 0.61.0
# Vulnerability DB:
#   Type: Light
#   Next Update: 2026-05-27 12:00:00 (UTC)

Trivy downloads a vulnerability database on first run. The database is cached under ~/.cache/trivy and updated automatically when you scan with a DB older than 12 hours. You can force an update with:

1
trivy image --download-db-only

Scanning Running Docker Images

The most useful scan for a homelab: check every image currently running for known CVEs.

Scan a Single Image

1
trivy image nginx:1.27-alpine

This produces a severity-sorted table of every CVE found in the OS packages inside that image. Critical findings are highlighted in red.

Scan All Running Containers

Combine docker ps with Trivy to scan your entire runtime:

1
2
3
4
docker ps --format '{{.Image}}' | sort -u | while read img; do
  echo "=== Scanning: $img ==="
  trivy image --severity HIGH,CRITICAL --quiet "$img"
done

Add --ignore-unfixed to skip CVEs that have no available fix — this reduces noise significantly in production-style scans.

1
trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:1.27-alpine

Example Output

nginx:1.27-alpine (alpine 3.21.3)
===============================
Total: 3 (HIGH: 2, CRITICAL: 1)

┌──────────┬────────────────┬──────────┬──────────────┬───────────────┐
│ Library  │ Vulnerability  │ Severity │ Installed Ver│ Fixed Version │
├──────────┼────────────────┼──────────┼──────────────┼───────────────┤
│ libcrypto3│ CVE-2026-1234 │ CRITICAL │ 3.3.0-r0     │ 3.3.1-r0      │
│ libssl3  │ CVE-2026-1235 │ HIGH     │ 3.3.0-r0     │ 3.3.1-r0      │
│ zlib     │ CVE-2026-1236 │ HIGH     │ 1.3.1-r0     │ 1.3.2-r0      │
└──────────┴────────────────┴──────────┴──────────────┴───────────────┘

Scanning for Misconfigurations and Secrets

Trivy is not just for CVEs. It also scans Dockerfiles and docker-compose files for misconfigurations and hardcoded secrets.

Scan Dockerfile Misconfigurations

1
trivy config ./docker-compose.yml

This catches issues like:

  • Containers running as root when they don’t need to
  • Missing health checks
  • Exposed ports without documented purpose
  • Privileged mode without justification
  • Environment variables that look like secrets

Scan for Secrets in Images

1
trivy image --scanners secret nginx:1.27-alpine

The secrets scanner checks for embedded API keys, tokens, passwords, and private keys in image layers. Useful before pushing custom images to a registry.

Full Multi-Scanner Run

1
trivy image --scanners vuln,secret,config my-custom-image:latest

Generating Actionable Reports

Plain terminal output is fine for interactive use, but for automated scans you want formats you can archive, email, or visualize.

HTML Report

1
2
trivy image --format template --template "@contrib/html.tpl" \
  -o report.html nginx:1.27-alpine

Install the template first:

1
2
3
mkdir -p ~/.trivy/templates
wget -O ~/.trivy/templates/html.tpl \
  https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/html.tpl

JSON Report for Scripting

1
trivy image --format json -o scan-results.json nginx:1.27-alpine

Use jq to extract only critical findings:

1
2
3
4
jq '.Results[]?.Vulnerabilities[]? |
     select(.Severity == "CRITICAL") |
     {PkgName, VulnerabilityID, InstalledVersion, FixedVersion, Title}' \
  scan-results.json

SARIF Output for IDE Integration

1
trivy image --format sarif -o results.sarif nginx:1.27-alpine

SARIF files can be opened in VS Code with the SARIF viewer extension.


Automating Weekly Scans with Systemd Timers

The most valuable setup for a homelab: unattended weekly scans that email or log results. Systemd timers are the cleanest way on Linux.

Create the Scan Script

1
sudo mkdir -p /opt/trivy-scanner

Write /opt/trivy-scanner/scan-all.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/bash
# Trivy weekly scan — all running images, HTML report
REPORT_DIR="/var/log/trivy-reports"
mkdir -p "$REPORT_DIR"
DATE=$(date +%Y-%m-%d-%H%M)
REPORT="$REPORT_DIR/trivy-scan-$DATE.html"

# Update vulnerability database
trivy image --download-db-only --quiet

# Scan each unique running image
docker ps --format '{{.Image}}' | sort -u | while read img; do
  echo "Scanning: $img"
  trivy image --severity HIGH,CRITICAL --ignore-unfixed \
    --format template --template "@/home/gntech/.trivy/templates/html.tpl" \
    --output "/tmp/trivy-partial-$DATE.html" \
    "$img" 2>/dev/null

  if [ -f "/tmp/trivy-partial-$DATE.html" ]; then
    sed -i "s|<title>|<title>$img - |" "/tmp/trivy-partial-$DATE.html"
    cat "/tmp/trivy-partial-$DATE.html" >> "$REPORT"
    rm "/tmp/trivy-partial-$DATE.html"
  fi
done

# Also check configs
trivy config --severity HIGH,CRITICAL /opt/projects/gntech-blog/docker/ \
  --format template --template "@/home/gntech/.trivy/templates/html.tpl" \
  --output "/tmp/trivy-config-$DATE.html" 2>/dev/null

if [ -f "/tmp/trivy-config-$DATE.html" ]; then
  cat "/tmp/trivy-config-$DATE.html" >> "$REPORT"
  rm "/tmp/trivy-config-$DATE.html"
fi

echo "Report written to: $REPORT"

Make it executable:

1
sudo chmod +x /opt/trivy-scanner/scan-all.sh

Create the Systemd Service

/etc/systemd/system/trivy-scan.service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[Unit]
Description=Trivy weekly vulnerability scan of running Docker images
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/opt/trivy-scanner/scan-all.sh
User=gntech
Group=docker
Nice=19
IOSchedulingClass=idle
Environment=HOME=/home/gntech

The Nice=19 and IOSchedulingClass=idle lines ensure the scan doesn’t compete with production workloads.

Create the Systemd Timer

/etc/systemd/system/trivy-scan.timer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Unit]
Description=Weekly Trivy vulnerability scan
Requires=trivy-scan.service

[Timer]
OnCalendar=weekly
Persistent=true
RandomizedDelaySec=30min

[Install]
WantedBy=timers.target

Enable and start:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable trivy-scan.timer
sudo systemctl start trivy-scan.timer

Check Timer Status

1
2
3
4
5
6
7
8
systemctl status trivy-scan.timer
# ● trivy-scan.timer - Weekly Trivy vulnerability scan
#   Trigger: Wed 2026-05-27 00:00:00 UTC; 10h left
#   Triggers: trivy-scan.service

# Force a manual run:
sudo systemctl start trivy-scan.service
journalctl -u trivy-scan.service --since "5 min ago"

Cron Alternative for Simpler Setups

If systemd timers feel heavy, a cron job works just as well:

1
2
# Run every Sunday at 3 AM
0 3 * * 0 /opt/trivy-scanner/scan-all.sh >> /var/log/trivy-cron.log 2>&1

Add with crontab -e or drop a file in /etc/cron.weekly/.


Integrating with a Monitoring Dashboard

Trivy JSON output can be visualized in Grafana or Grafana Alloy. A lightweight alternative: serve the HTML reports with a one-liner HTTP server and catch them in your monitoring stack.

Serve Reports via Docker

1
2
3
docker run -d --name trivy-reports --restart unless-stopped \
  -v /var/log/trivy-reports:/usr/share/nginx/html:ro \
  -p 8088:80 nginx:alpine

Then check http://homelab-host:8088 for the latest scan reports.

Extract Key Metrics with jq

1
2
3
4
5
6
# Count critical/high per report
for f in /var/log/trivy-reports/*.json; do
  echo "$(basename $f):"
  echo "  CRITICAL: $(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' $f)"
  echo "  HIGH:     $(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="HIGH")] | length' $f)"
done

Keeping Images Up to Date

Scanning is only half the battle. When Trivy flags a critical CVE, the fix is almost always a newer version of the base image. A practical workflow:

  1. Scan runs weekly, report lands in /var/log/trivy-reports/
  2. Review report for CRITICAL findings where a fix exists
  3. Pull the fixed image: docker pull nginx:1.27-alpine
  4. Recreate containers: docker compose up -d
  5. Re-run scan to confirm all CVEs are resolved

For images you build yourself, rebuild with updated base images and redeploy. If you run Watchtower (covered in a previous post), it can handle the pulling step automatically — but Trivy gives you the visibility to decide which updates matter.


Key Takeaways

  • Trivy scans images, filesystems, configs, and secrets — it is the single tool you need for container security in a homelab.
  • Use --severity HIGH,CRITICAL --ignore-unfixed to focus on actionable findings and ignore noise.
  • Systemd timers with idle scheduling run scans without disrupting workloads. Nice=19 keeps CPU impact minimal.
  • HTML or JSON reports make results reviewable at a glance and storable for trend analysis.
  • Scan first, then update — Trivy tells you what to fix. Your image update strategy (Watchtower, manual pull, or rebuild) closes the loop.

Security is a process, not a one-time task. An automated weekly scan with Trivy keeps your homelab safe with minimal effort — and gives you the confidence that your containers aren’t running known-critical vulnerabilities between updates.