Tech Expert & Vibe Coder

With 14+ years of experience, I specialize in self-hosting, AI automation, and Vibe Coding – building applications using AI-powered tools like Google Antigravity, Dyad, and Cline. From homelabs to enterprise solutions.

Setting up automated container image vulnerability scanning with Trivy and ntfy notifications

Why I Added Container Vulnerability Scanning

I run about 40 Docker containers across my Proxmox homelab. Most are stable services I've been using for months—n8n, Synology Active Backup, Nginx Proxy Manager, and various media tools. I update them manually when I remember, usually triggered by a changelog I happen to see or when something breaks.

The problem: I had no systematic way to know if any of my running images had known vulnerabilities. I wasn't checking. I wasn't even sure which images were outdated beyond looking at tags.

I didn't want a full enterprise scanning platform. I just needed something that would:

  • Scan the images I'm actually running
  • Tell me about critical issues
  • Not require me to log into dashboards or check reports manually

That's when I set up Trivy with ntfy for push notifications.

My Setup: Trivy in a Container

Trivy is an open-source vulnerability scanner from Aqua Security. It scans container images, filesystems, and git repositories for known CVEs. I only care about container images.

I run Trivy itself as a Docker container on one of my Proxmox VMs. This VM already handles scheduled tasks and monitoring scripts, so adding Trivy made sense.

Here's the basic Trivy container I use:

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v trivy-cache:/root/.cache/ \
  aquasec/trivy:latest image \
  --severity HIGH,CRITICAL \
  --format json \
  nginx:latest

This mounts the Docker socket so Trivy can access running containers. The cache volume speeds up subsequent scans by storing vulnerability databases locally.

I tested this manually first on a few images I knew were outdated. It worked immediately—no configuration files, no API keys, just point it at an image.

Scanning All Running Containers

I needed to scan everything currently running, not just individual images. I wrote a simple bash script that:

  • Lists all running containers
  • Extracts their image names
  • Runs Trivy against each one
  • Aggregates results
#!/bin/bash

IMAGES=$(docker ps --format '{{.Image}}' | sort -u)

for IMAGE in $IMAGES; do
  echo "Scanning $IMAGE..."
  docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v trivy-cache:/root/.cache/ \
    aquasec/trivy:latest image \
    --severity HIGH,CRITICAL \
    --format json \
    "$IMAGE" > "/tmp/trivy-${IMAGE//\//_}.json"
done

This creates a JSON file for each scanned image. I filter for HIGH and CRITICAL severities only because I don't have time to fix every low-priority CVE in base images I don't control.

The script takes about 5 minutes to scan all 40 containers on the first run. Subsequent runs are faster thanks to the cache.

Parsing Results and Deciding What Matters

Trivy outputs detailed JSON with every vulnerability found. Most of the time, this includes dozens of issues in base OS packages that I can't fix without waiting for upstream image updates.

I needed to filter noise. My rules:

  • Only notify for CRITICAL vulnerabilities
  • Ignore vulnerabilities in packages I'm not directly using (like kernel modules in Alpine base images)
  • Group results by container, not by individual CVE

I added a Python script to parse the JSON and extract actionable information:

import json
import glob

critical_findings = []

for file in glob.glob("/tmp/trivy-*.json"):
    with open(file) as f:
        data = json.load(f)
        
    if not data.get("Results"):
        continue
        
    for result in data["Results"]:
        vulns = result.get("Vulnerabilities", [])
        critical = [v for v in vulns if v["Severity"] == "CRITICAL"]
        
        if critical:
            image_name = file.replace("/tmp/trivy-", "").replace(".json", "")
            critical_findings.append({
                "image": image_name,
                "count": len(critical),
                "cves": [v["VulnerabilityID"] for v in critical[:5]]
            })

print(json.dumps(critical_findings, indent=2))

This gives me a clean summary: which containers have critical issues and how many.

Sending Notifications with ntfy

I use ntfy for all my homelab notifications. It's a simple HTTP-based notification service that I self-host. No app required—I just