CVE Radar ships as a single container: Express API + built Vite UI on port 3001.
Docker Hub page content (short + full overview, UI checklist): docs/dockerhub/
Push to Hub via API: DOCKERHUB_USERNAME=raminnietzsche DOCKERHUB_TOKEN=... npm run dockerhub:update
docker pull raminnietzsche/cve-radar:latest
docker run --rm -p 3001:3001 raminnietzsche/cve-radar:latestOptional NVD rate limits — pass an API key:
docker run --rm -p 3001:3001 -e NVD_API_KEY=your-key raminnietzsche/cve-radar:latestProduction API authentication (optional shared secret):
docker run --rm -p 3001:3001 \
-e API_SECRET=your-long-random-secret \
-e VITE_API_KEY=your-long-random-secret \
raminnietzsche/cve-radar:latestWhen API_SECRET is set, clients must send X-Api-Key or Authorization: Bearer. GET /api/health remains open for probes. See SECURITY.md.
Compose (published image):
docker compose -f docker-compose.hub.yml up -dProduction secrets (Docker Secrets / *_FILE env): see docs/self-hosted/SECRETS.md and docker-compose.secrets.example.yml.
docker compose up --build
# or
docker build -t cve-radar:local .
docker run --rm -p 3001:3001 cve-radar:localWorkflow: .github/workflows/release-docker-hub.yml
On git tag v* (e.g. v1.1.0), GitHub Actions builds and pushes to:
docker.io/<DOCKERHUB_USERNAME>/cve-radar
The workflow job uses environment: docker. Add secrets in either place:
| Where | Path |
|---|---|
| Recommended | Settings → Environments → docker → Environment secrets |
| Alternative | Settings → Secrets and variables → Actions → Repository secrets |
| Secret | Description |
|---|---|
DOCKERHUB_USERNAME |
Docker Hub user or org (e.g. raminnietzsche) |
DOCKERHUB_TOKEN |
Access token with Read, Write, and Delete |
Important: Read/Write alone can push images but Hub returns 403 insufficient scope when updating description/overview via API. Regenerate the token with Delete checked, then update the secret in Environments → docker.
Repository-level secrets alone are not visible to the job unless you remove environment: docker from the workflow or duplicate the secrets under the docker environment.
After secrets are set, create a release tag (git push origin v1.1.0) to publish X.Y.Z and latest. Pushes to main alone do not publish a new Hub image.
Remove legacy sha-*, X.Y, and X tags (keeps latest and full semver X.Y.Z):
DOCKERHUB_USERNAME=raminnietzsche DOCKERHUB_TOKEN='dckr_pat_...' npm run dockerhub:pruneDry run: DRY_RUN=1 npm run dockerhub:prune
| Variable | Purpose |
|---|---|
NVD_API_KEY |
NVD upstream rate limit |
RATE_LIMIT_SCAN_PER_MIN |
App middleware: max POST /scan per IP/min (default 12) |
RATE_LIMIT_WATCH_PER_MIN |
App middleware: max POST /watch per IP/min (default 120) |
TRUST_PROXY_HOPS |
When behind nginx/Traefik, set to 1 so rate limits use the real client IP from the trusted proxy (default: socket address only; X-Forwarded-For is ignored) |
SCAN_TIMEOUT_MS |
Abort long scan/watch requests |
REDIS_URL |
Optional Redis URL for shared NVD/GitHub/OSV/RSS/translation cache across replicas (e.g. redis://redis:6379 in Compose) |
CACHE_MAX_ENTRIES |
In-memory cache cap when Redis is not used (default 2000) |
DATA_DIR |
Writable server data root (default ./data; Compose uses /app/data volume) |
When you run more than one CVE-Radar container behind a load balancer, set REDIS_URL so upstream API responses are cached once for all replicas. docker compose up (local build) starts a redis:7-alpine sidecar and wires REDIS_URL=redis://redis:6379 automatically.
Without REDIS_URL, each process keeps its own in-memory cache (default for npm run dev).
Verify shared cache: curl -s 'http://localhost:3001/api/health?detailed=true' | jq .cache should show "backend": "redis" when Redis is connected.
Set DATABASE_URL to persist scan metadata server-side (stack, mode, counts, source health). Without it, history endpoints return 503 DATABASE_DISABLED and scans behave as today (client-only localStorage cache).
DATABASE_URL=postgres://user:pass@postgres:5432/cve_radarRead API (v1 only):
GET /api/v1/scans/history?limit=50GET /api/v1/scans/trends?days=30
Schema migration runs automatically from server/db/migrations/001_scan_history.sql on first pool connect.
Local Postgres with Compose profile:
# .env — when postgres profile is active
DATABASE_URL=postgres://cve_radar:cve_radar@127.0.0.1:5432/cve_radar
docker compose --profile postgres up --builddocker compose up mounts a named volume at /app/data so container recreation does not wipe server-side state (future mirror caches, audit files, etc.).
docker compose up --build -d
docker compose exec cve-radar sh -c 'echo ok > /app/data/.persist-test'
docker compose restart cve-radar
docker compose exec cve-radar cat /app/data/.persist-test # → okHub image (no local build):
docker compose -f docker-compose.hub.yml up -dBackup:
docker run --rm -v cve-radar-data:/data -v "$PWD":/backup alpine \
tar czf /backup/cve-radar-data.tgz -C /data .Restore: extract archive into a new volume before compose up.
See Release process for tagging workflow.
docker login
docker build -t raminnietzsche/cve-radar:latest .
docker push raminnietzsche/cve-radar:latestReplace raminnietzsche with your Docker Hub namespace if different.