tutorial

How to Monitor Grype Container Scanning with Vigilmon

Grype is an open-source vulnerability scanner from Anchore that scans container images, filesystems, and SBOMs for known CVEs. Teams integrate Grype into CI/...

Grype is an open-source vulnerability scanner from Anchore that scans container images, filesystems, and SBOMs for known CVEs. Teams integrate Grype into CI/CD pipelines to catch vulnerabilities before images are deployed, and run it on schedules against production registries to detect newly disclosed CVEs in already-deployed software.

The catch: Grype itself can go wrong. The vulnerability database can become stale. CI pipeline integrations can silently break. Scheduled scans can stop running without raising any obvious errors. When this happens, you're operating under a false sense of security — you think you're scanning, but you're not.

Vigilmon provides external monitoring that catches these failures — broken CI integrations, missed scan schedules, stale vulnerability databases, and more. This tutorial walks through setting up comprehensive monitoring for your Grype-based container security workflow.


Why Grype scanning infrastructure needs external monitoring

Grype is a tool, not a service — but the processes and pipelines that run Grype are infrastructure, and infrastructure fails:

  • Scheduled scans stop running — a cron job or Kubernetes CronJob running nightly Grype scans silently stops executing; new CVEs in your running containers go undetected for days or weeks
  • Vulnerability database goes stale — Grype downloads its vulnerability database (grype db update) from an Anchore CDN; if the update step fails, Grype keeps scanning but misses vulnerabilities disclosed after the database was last refreshed
  • CI pipeline step breaks — a Docker base image update or dependency change breaks the Grype scanning step; it either fails silently or starts being skipped by teams who'd rather not block deployments
  • Reporting pipeline fails — vulnerability scan results need to reach SIEM systems, ticketing tools, or dashboards; a broken reporting integration means findings accumulate unseen
  • Registry scan service crashes — if you run a persistent Grype-based registry scanning service (Anchore Engine, Harbor with Trivy/Grype integration), it can go down while your registry keeps accepting image pushes

External monitoring with Vigilmon catches all of these — not by inspecting Grype's internals, but by verifying that your scanning processes are actually running and that your reporting services are reachable.


What you'll need

  • Grype installed and integrated into at least one pipeline or running on a schedule
  • A place to expose a health endpoint (a small web service or a reporting pipeline)
  • A free Vigilmon account

Step 1: Add Vigilmon heartbeats to scheduled Grype scans

The most important thing to monitor is whether your scheduled scans are actually running. Vigilmon's heartbeat monitors fire an alert when they don't receive a ping within the expected interval.

Nightly cron job pattern

If you run Grype on a schedule via cron:

#!/bin/bash
# /opt/scripts/grype-nightly-scan.sh

set -euo pipefail

IMAGE_LIST=(
    "your-registry.io/app-api:latest"
    "your-registry.io/app-worker:latest"
    "your-registry.io/app-scheduler:latest"
)

REPORT_DIR="/var/reports/grype/$(date +%Y-%m-%d)"
mkdir -p "$REPORT_DIR"

# Update vulnerability database first
grype db update

for image in "${IMAGE_LIST[@]}"; do
    image_slug=$(echo "$image" | tr '/:' '_')
    
    # Scan with JSON output for downstream processing
    grype "$image" \
        --output json \
        --file "$REPORT_DIR/${image_slug}.json" \
        --fail-on critical 2>&1 | tee "$REPORT_DIR/${image_slug}.log"
done

# Only ping Vigilmon if all scans completed successfully
curl -sf "https://vigilmon.online/api/heartbeat/your-heartbeat-id" || true

The key pattern: curl the Vigilmon heartbeat URL only at the end of the script, after all scans have completed. If the script fails at any point — database update failure, scan error, disk full — the heartbeat ping is never reached.

In Vigilmon:

  1. Log in to vigilmon.onlineMonitors → New Monitor
  2. Choose Heartbeat
  3. Name: grype-nightly-scan
  4. Expected interval: 24 hours
  5. Grace period: 2 hours (scans can run long if images are large)
  6. Save and copy the ping URL

Add the cron entry:

# Run nightly at 02:00
0 2 * * * /opt/scripts/grype-nightly-scan.sh >> /var/log/grype-scan.log 2>&1

Kubernetes CronJob pattern

If your scans run as Kubernetes CronJobs:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: grype-nightly-scan
  namespace: security
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: grype-scanner
              image: anchore/grype:latest
              command: ["/bin/sh", "-c"]
              args:
                - |
                  grype db update && \
                  grype your-registry.io/app-api:latest --output json > /reports/api-scan.json && \
                  grype your-registry.io/app-worker:latest --output json > /reports/worker-scan.json && \
                  curl -sf "https://vigilmon.online/api/heartbeat/your-heartbeat-id"
              env:
                - name: GRYPE_DB_UPDATE_URL
                  value: "https://toolbox-data.anchore.io/grype/databases/listing.json"
              volumeMounts:
                - name: reports
                  mountPath: /reports
          volumes:
            - name: reports
              emptyDir: {}

Step 2: Monitor Grype database freshness

Grype's effectiveness depends entirely on having an up-to-date vulnerability database. A scanner with a 2-week-old database is nearly useless against recently disclosed CVEs. Build a small service that checks the database age and exposes it as a health endpoint:

from fastapi import FastAPI, Response
import subprocess
import json
import re
from datetime import datetime, timezone, timedelta

app = FastAPI()

MAX_DATABASE_AGE_HOURS = 24

@app.get("/health/grype-db")
async def grype_db_health(response: Response):
    try:
        result = subprocess.run(
            ["grype", "db", "status", "--output", "json"],
            capture_output=True,
            text=True,
            timeout=30
        )

        if result.returncode != 0:
            response.status_code = 503
            return {
                "status": "error",
                "error": "grype db status failed",
                "stderr": result.stderr
            }

        db_status = json.loads(result.stdout)
        built_at_str = db_status.get("built", "")

        # Parse the database built timestamp
        built_at = datetime.fromisoformat(built_at_str.replace("Z", "+00:00"))
        age = datetime.now(timezone.utc) - built_at
        age_hours = age.total_seconds() / 3600

        if age_hours > MAX_DATABASE_AGE_HOURS:
            response.status_code = 503
            return {
                "status": "stale",
                "db_built_at": built_at_str,
                "age_hours": round(age_hours, 1),
                "max_age_hours": MAX_DATABASE_AGE_HOURS,
                "schema_version": db_status.get("schemaVersion")
            }

        return {
            "status": "fresh",
            "db_built_at": built_at_str,
            "age_hours": round(age_hours, 1),
            "schema_version": db_status.get("schemaVersion")
        }

    except subprocess.TimeoutExpired:
        response.status_code = 503
        return {"status": "timeout", "error": "grype db status timed out"}
    except Exception as e:
        response.status_code = 503
        return {"status": "error", "error": str(e)}

@app.get("/health")
async def health():
    return {"status": "ok"}

Add this as an HTTP monitor in Vigilmon:

  • URL: https://grype-health.internal/health/grype-db
  • Interval: 1 hour
  • Expected status: 200
  • Expected body contains: "fresh"

When this monitor goes down, you know Grype's vulnerability database hasn't been refreshed in over 24 hours — your scans are running but finding fewer vulnerabilities than they should.


Step 3: Monitor a Grype-based registry scanning service

If you run a persistent service that scans images as they are pushed to your registry (Anchore Engine, or a custom webhook listener + Grype), that service needs uptime monitoring too:

# Example lightweight registry scan webhook service
from fastapi import FastAPI, Request
import subprocess
import asyncio

app = FastAPI()

@app.get("/health")
async def health():
    # Verify Grype binary is present and usable
    try:
        result = subprocess.run(
            ["grype", "--version"],
            capture_output=True,
            text=True,
            timeout=10
        )
        return {
            "status": "ok",
            "grype_version": result.stdout.strip()
        }
    except Exception as e:
        return {"status": "down", "error": str(e)}, 503

@app.post("/scan")
async def scan_image(request: Request):
    body = await request.json()
    image = body["image"]
    result = subprocess.run(
        ["grype", image, "--output", "json"],
        capture_output=True,
        text=True,
        timeout=300
    )
    return {"scan_result": result.stdout}

Add an HTTP monitor in Vigilmon:

  • URL: https://grype-scanner.internal/health
  • Interval: 1 minute
  • Expected status: 200

Step 4: Monitor CI pipeline scan enforcement

If your CI pipeline uses Grype to block deployments on critical CVEs, you want to know when that enforcement breaks. The cleanest way is a heartbeat that's pinged by your CI pipeline after each successful scan run:

Add a step to your CI configuration (GitHub Actions, GitLab CI, etc.):

# GitHub Actions example
- name: Scan container image with Grype
  run: |
    grype ${{ env.IMAGE_TAG }} \
      --output sarif \
      --file grype-results.sarif \
      --fail-on critical

- name: Notify Vigilmon - scan completed
  if: always()
  run: |
    if [ "${{ job.status }}" == "success" ]; then
      curl -sf "https://vigilmon.online/api/heartbeat/grype-ci-scan-id"
    fi

In Vigilmon, set the heartbeat interval to slightly longer than your typical CI cadence. If your team deploys multiple times a day, a 12-hour heartbeat means you'll know within half a day if Grype scanning stopped being run in CI.


Step 5: Monitor the Grype vulnerability database update service

Grype fetches its database from Anchore's CDN. If your infrastructure blocks outbound access to the CDN (firewall change, proxy misconfiguration), database updates silently fail. Monitor the CDN endpoint reachability:

  1. In Vigilmon → Monitors → New Monitor → HTTP
  2. URL: https://toolbox-data.anchore.io/grype/databases/listing.json
  3. Interval: 30 minutes
  4. Expected status: 200

This tells you when your network loses access to the Grype update CDN — before your next scheduled scan runs with a stale database.


Step 6: Set up alert channels

When Grype monitoring fails, the security team needs to know immediately:

  1. In Vigilmon → Alert Channels → Add Channel → Email
  2. Add your security team's email or on-call address
  3. Assign to all Grype monitors

For PagerDuty or Opsgenie (critical security infrastructure):

  1. Alert Channels → Add Channel → Webhook
  2. Paste your PagerDuty Events API webhook URL
  3. Assign to your most critical monitors: nightly scan heartbeat, database freshness

Example Vigilmon alert payload for a missed scan heartbeat:

{
  "monitor_name": "grype-nightly-scan",
  "status": "missing",
  "expected_every": "86400s",
  "last_seen_at": "2026-07-01T02:15:00Z",
  "missing_for_seconds": 90000
}

This tells your security team that the nightly vulnerability scan hasn't run in over 25 hours — time to investigate before the next deployment goes out without security validation.


Step 7: Create a security tooling status page

For engineering teams that rely on Grype as a gate in their deployment pipeline, a status page reduces friction:

  1. In Vigilmon → Status Pages → New Status Page
  2. Name: "Container Security Pipeline"
  3. Add monitors: nightly scan heartbeat, database freshness, CI scan heartbeat, registry scanner service, CDN reachability
  4. Group by: "Scan Execution", "Vulnerability Database", "CI Pipeline"
  5. Publish

Share this page in your security runbook and developer documentation. When a deployment is blocked and the developer doesn't know why, the status page tells them instantly whether the Grype scanner itself is the problem.


Debugging Grype alert failures

# 1. Check if cron job is scheduled
crontab -l | grep grype

# 2. Check last cron execution
grep grype /var/log/cron

# 3. Check database status
grype db status

# 4. Manually update the database
grype db update
grype db status  # Verify the new date

# 5. Test a scan manually
grype your-registry.io/app-api:latest --output table

# 6. Check Grype CDN reachability
curl -I https://toolbox-data.anchore.io/grype/databases/listing.json

# 7. Check available disk space (large databases fail silently on full disks)
df -h ~/.cache/grype/

Monitoring summary

| Monitor | Type | Interval | What it catches | |---------|------|----------|-----------------| | Nightly scan heartbeat | Heartbeat | 24h | Scheduled scan not running | | CI scan heartbeat | Heartbeat | Per build cadence | CI enforcement broken | | Grype DB freshness endpoint | HTTP | 1h | Database update failing | | Registry scanner /health | HTTP | 1 min | Scanner service down | | Anchore CDN reachability | HTTP | 30 min | Network can't reach update CDN |


What's next

  • Slack integration — route Grype heartbeat misses to #security-alerts so the security team sees them immediately alongside other security signals
  • Multi-environment heartbeats — create separate heartbeat monitors for dev, staging, and production scan jobs to ensure each environment is being scanned independently
  • SSL monitoring — if you run a private Grype database mirror or an internal Anchore Engine instance over HTTPS, Vigilmon monitors the TLS certificate to prevent silent expiry

Start free at vigilmon.online — no credit card, your first heartbeat monitor is live in under two minutes.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →