Self-host
Runs on your infrastructure
One container serves the API and the dashboard on a single port, with the database in a directory you mount. The dashboard loads no third-party fonts, scripts or analytics.
Security notes
- There is no authentication on any endpoint. Anyone who can reach the port can read every finding and start scans. Don't publish the port to the internet. Keep it on an internal network, or behind a reverse proxy that enforces authentication and TLS.
- Scan credentials travel to the service. They're held in memory for the request and never written to the database or logs. Use TLS end to end if scan requests cross an untrusted network.
- SSH host keys are accepted automatically. There is no host-key verification, so only scan over networks where you accept that risk.
- Use least privilege. The collector only runs read-only commands, so give it its own unprivileged account on each target, with no sudo.
Try it with demo data
Demo mode seeds a fictional twelve-host fleet into an empty database and refuses every route that reaches the network: scans, discovery sweeps and connection tests. Leave it off on any instance you actually scan with.
# build from source, then run on localhost only
git clone https://github.com/reprodev/cvedeck
cd cvedeck
docker build -t cvedeck .
docker run -d --name cvedeck-demo \
-p 127.0.0.1:3325:8000 \
-e CVEDECK_DEMO_MODE=true \
cvedeck
Then open http://localhost:3325.
Scan your own fleet
Docker
Mount a data directory. The database is written there and survives container replacement.
docker run -d --name cvedeck \
-p 127.0.0.1:3325:8000 \
-v /srv/cvedeck:/data \
-e PUID="$(id -u)" -e PGID="$(id -g)" \
--restart unless-stopped \
cvedeck
Then add hosts by address, such as 192.168.1.50, or sweep a subnet such as
192.0.2.0/24 to find hosts to enrol.
Docker Compose
From a checkout, copy the example environment file and set the host port, data directory and user IDs.
The port defaults to 3325.
cp .env.example .env
docker compose up -d
Native Linux (systemd)
For hosts without Docker. Needs Python 3.11 or later, plus Node and npm to build the dashboard. The
service binds to 127.0.0.1:8000 only, ready for a TLS reverse proxy, and the installer adds a
daily feed-refresh timer.
git clone https://github.com/reprodev/cvedeck
cd cvedeck
sudo ./deploy/install.sh
Keep threat intel fresh
The CISA KEV and FIRST EPSS feeds are refreshed on request, and there is no scheduler in the app yet. Both publish daily, so a daily refresh is enough. With Docker, add a cron entry on the host.
Crontab entry (crontab -e), refreshing daily at 03:17:
17 3 * * * curl -fsS -X POST http://localhost:3325/api/feeds/refresh >/dev/null
Check each feed's age, record count and staleness:
curl -fsS http://localhost:3325/api/feeds
Refreshed intel applies to findings at their host's next scan. How the feeds are used.
Running it day to day
- Network access. Outbound TCP 22 to your Linux targets, and HTTPS to the data sources:
OSV.dev, CISA and FIRST, plus NVD if you enable it. For air-gapped networks, point the feed URLs at an
internal mirror, for example
https://mirror.example.com/. - Reverse proxies. Scans run within the request and respond only when the whole batch finishes. Raise your proxy's read timeout to several minutes, or multi-host scans get cut off.
- SSH keys. For repeat fleet scans, mount a dedicated key read-only and point
CVEDECK_DEFAULT_SSH_KEY_PATHandCVEDECK_DEFAULT_SSH_USERat it. The key file is re-read on every scan, so rotating it needs no restart. - Backups. With SQLite, everything lives in one file,
cvedeck.db, in the data directory. Stop the container and copy it. PostgreSQL is supported throughCVEDECK_DB_URL. - Upgrades. The schema migrates forward automatically on start. Back up first, and
check
/api/health, which reports the running version.
The full deployment guide, including every setting, is in the repository.