Chainguard Comparison
Collects CVE posture data comparing Hummingbird and Chainguard container images and surfaces the results in the Hummingbird dashboard under the Competitive tab.
Features
- Fetches Hummingbird CVE data from the catalog API — the same source used by
images.redhat.com, updated hourly by the scan pipeline - Scans Chainguard images with Grype (the only available source for Chainguard CVE data)
- Stores results in PostgreSQL for trend tracking and historical comparison
- Dashboard shows per-image CVE counts, severity breakdowns (C/H/M/L), delta (HB minus CG), 30-day trend charts, and aggregate summary stats
- Runs every 6 hours as a Kubernetes CronJob
Prerequisites
- Access to the Hummingbird cluster (PostgreSQL, image pull credentials)
- The
chainguard_comparisonpackage from competitive-tools installed in the container image
Deployment
The tool is deployed as a Kubernetes CronJob. The container image is built by
Konflux from chainguard-comparison/Containerfile.
Environment variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string, e.g. postgresql://user:pass@host/db |
GRYPE_TIMEOUT |
No | Per-image Grype scan timeout in seconds (default: 300) |
SENTRY_DSN |
No | Sentry DSN for error reporting |
How it works
- Discovery — lists Hummingbird images from the catalog API and Chainguard images via the OCI distribution API
- Matching — pairs images by name using the alias map in
competitive-tools(e.g.nodejs↔node,postgresql↔postgres) - HB collection — calls
GET /v1/images/{name}/vulnerabilities/{tag}on the catalog API; no image pull or local scan required - CG collection — pulls each Chainguard image and runs
grypelocally; Chainguard has no publicly accessible pre-scanned CVE feed - Storage — writes both sides to the
chainguard_scanstable in PostgreSQL
Dashboard
The /competitive page in hummingbird-dashboard shows:
- Summary cards: total images compared, count worse/better/same
- Sortable table with per-image CVE counts and delta
- Inline severity breakdown: Critical / High / Medium / Low
- Per-image 30-day trend chart (click trend on any row)
- Filter box for quick image lookup
Database schema
CREATE TABLE chainguard_scans (
id SERIAL PRIMARY KEY,
collected_at TIMESTAMPTZ NOT NULL,
source TEXT NOT NULL, -- 'hummingbird' or 'chainguard'
image_name TEXT NOT NULL,
tag TEXT NOT NULL DEFAULT 'latest',
image_ref TEXT,
total_cves INTEGER NOT NULL DEFAULT 0,
critical INTEGER NOT NULL DEFAULT 0,
high INTEGER NOT NULL DEFAULT 0,
medium INTEGER NOT NULL DEFAULT 0,
low INTEGER NOT NULL DEFAULT 0,
negligible INTEGER NOT NULL DEFAULT 0,
unknown INTEGER NOT NULL DEFAULT 0
);
The table is created by the Alembic migration
b4e7f8a1c3d5_add_chainguard_scans.py.
Development
cd chainguard-comparison
pip install -e ".[dev]"
python -m pytest tests/
To run the collector locally against a development database:
export DATABASE_URL=postgresql://postgres:dev@localhost:5432/events
python -m cg_comparison
License
GPL-3.0-or-later