Run it on your own infrastructure.
The scan engine, the dashboard, the worker and every check they run are open source. Clone the repository, bring your own datastores or use the bundled ones, and nothing leaves your network.
Tested on Docker Engine 24+ with Compose v2. About ten minutes end to end, most of it the first image build.
Why self-host
The reasons operators actually give us — each is a property of the deployment, not a plan tier.
Your data stays put
Scan targets, findings and reports are written to a database you run. There is no phone-home, no telemetry beacon and no license check.
Auditable checks
Every check the engine runs is source you can read and change. Security teams that have to justify a finding can point at the code that produced it.
Air-gapped and regulated networks
The stack runs without outbound internet. AI enrichment and breach lookups are the only network-dependent features, and both degrade to their heuristics rather than failing.
No asset limit
Self-hosted has no seat count and no asset cap. What you can scan is bounded by the hardware you give it.
Requirements
A single host is enough to start. The worker is the part that wants CPU — scans are the workload.
- Runtime
- Docker Engine 24+ with Compose v2 (Linux or macOS)
- CPU / memory
- 4 vCPU · 8 GB RAM for a comfortable single-host install
- Disk
- 20 GB — images, MongoDB data, and stored reports
- Datastores
- Bundled: MongoDB 7 (single-node replica set), Redis 7, MinIO. Point at your own instead if you already run them.
- Outbound network
- Only to your scan targets. Add outbound HTTPS if you enable AI enrichment or breach lookups.
- From source
- Node 20 + pnpm, if you would rather run the apps directly than the built images.
MongoDB must be a replica set — the app uses transactions. The bundled compose file initialises a single-node one for you.
Install
Six steps. Copy them in order; nothing here is a placeholder except the values you choose.
Clone and create your .env
The example file documents every variable. Copying it is the only configuration step that is not optional.
git clone https://github.com/021flow/heimdallx.gitcd heimdallxcp .env.example .envGenerate the two secrets
Sessions are signed with SESSION_SECRET, and the local administrator account is how you sign in the first time.
openssl rand -hex 32 # → SESSION_SECRETopenssl rand -base64 24 # → SELFHOST_ADMIN_PASSWORDnode scripts/generate-totp-secret.mjs # → SELFHOST_ADMIN_TOTP_SECRET + otpauth URISet the three required values
Open .env and fill these in. A self-hosted instance runs in production mode, which disables the development login — the admin email, password and authenticator code are the way in until you wire Google or Apple sign-in.
SESSION_SECRET=<openssl rand -hex 32>SELFHOST_ADMIN_PASSWORD=<openssl rand -base64 24>SELFHOST_ADMIN_TOTP_SECRET=<Base32 seed from the generator>The password must be at least 12 characters: /auth/local is reachable from wherever you expose the instance.
Start the stack
The first run builds the API, worker and dashboard images and initialises the MongoDB replica set. Later runs start in seconds.
docker compose up -ddocker compose psCheck it is healthy
/health answers as soon as the API process is up and reports which features are actually configured. /ready additionally proves MongoDB and Redis are reachable.
curl -s http://localhost:6040/health# → {"ok":true,"service":"heimdallx-api","features":{…}}Sign in and run a scan
Open the dashboard and sign in with the admin email, password and 6-digit authenticator code from step 3. Enter a domain you own and watch the pipeline stream live.
open http://localhost:6000/app/
Configuration
The variables that matter for a self-hosted install. Everything missing simply disables that feature — the platform boots without any external credentials.
SESSION_SECRETrequiredSigning key for session tokens. 32 random bytes; rotating it logs everyone out.
SELFHOST_ADMIN_EMAILrequiredThe local administrator account. Enables POST /auth/local together with the two factors below.
SELFHOST_ADMIN_PASSWORDrequiredPassword for that account, 12 characters minimum. Compared in constant time; rate-limited to 10 attempts per 5 minutes per IP.
SELFHOST_ADMIN_TOTP_SECRETrequiredCanonical Base32 seed from scripts/generate-totp-secret.mjs. Store it in the operator secret manager and enter the fresh 6-digit code from an authenticator app; Redis prevents code reuse.
SELF_HOSTEDrecommendedSet true and the plan quotas stop applying — assets, scans per month, modules, monitor cadence, AI tokens, log ingestion. Those limits bound the hosted service's cost of goods; on your hardware, with your own model key, there is nothing of ours to bound.
MONGO_URLoptionalDefaults to the bundled replica set. Point it at your own cluster or an Atlas SRV URI — the connection-string shape is identical.
REDIS_URLoptionalJob queue and realtime fan-out. Defaults to the bundled Redis.
ANTHROPIC_API_KEYoptionalTurns on AI analysis, the copilot and AI triage. Without it those surfaces fall back to their heuristics and say so on screen.
AI_MODEL_DEEP / AI_MODEL_FASToptionalModel per workload. FAST carries the interactive volume and should stay a Haiku-class model.
PUBLIC_WEB_URL / PUBLIC_API_URLoptionalSet both when you expose the instance on a domain — they drive CORS, cookies and OAuth redirects.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECREToptionalAdds Google sign-in for the rest of your team. Apple sign-in has its own key trio.
HIBP_API_KEY / GITHUB_TOKENoptionalBreach lookups and higher GitHub rate limits for the exposure and code modules.
ENABLE_PORT_SCANoptionalAllows active port scanning. Off by default and consent-gated — only turn it on for assets you are authorised to probe.
STORAGE_DRIVER / S3_*optionalWhere reports and artefacts are stored. The bundled MinIO is S3-compatible; swap in a real bucket by changing these.
Every variable, with comments, lives in .env.example.
Operating it
The four things you will actually do after the install.
Upgrade
Pull, rebuild, restart. Schema changes are applied lazily on read, so there is no migration step to run.
git pulldocker compose builddocker compose up -d
Back up and restore
MongoDB holds everything durable — scans, findings, assets, reports. Snapshot it on whatever schedule your policy asks for.
docker compose exec -T mongo mongodump --archive --gzip > heimdallx.gzdocker compose exec -T mongo mongorestore --archive --gzip --drop < heimdallx.gz
Watch it
The API logs requests and the worker logs each pipeline stage. /ready is the probe to point a load balancer or Kubernetes at.
docker compose logs -f api workercurl -s http://localhost:6040/ready
Scale the scanning
Scans are queued jobs, so throughput is a worker count. Add workers before you add API instances.
docker compose up -d --scale worker=3
Kubernetes
For anything beyond a single host there are manifests in infra/k8s: Deployments for the API, worker, dashboard and landing, an HPA, a PodDisruptionBudget, a NetworkPolicy and a sticky-session Ingress (Socket.IO's polling handshake needs it). The prod overlay expects managed MongoDB and Redis.
docker build -f infra/docker/Dockerfile.api -t <registry>/heimdallx-api:<tag> .kubectl apply -k infra/k8s/overlays/prod
infra/k8s/README.md walks through the image build, the Secret and the ingress in full.
Self-hosted or Cloud
Everything the platform does is in the repository. What the hosted service adds is the part that has to be operated — feeds with subscriptions behind them, metered ingestion, managed AI.
| Self-hosted or Cloud | Self-hosted | Cloud |
|---|---|---|
| Scan engine · dashboard · worker | ✓ | ✓ |
| Findings · fix-first queue · assets · attack surface | ✓ | ✓ |
| EASM discovery · breach simulation · compliance | ✓ | ✓ |
| Integrations + CI gate (SARIF) | ✓ | ✓ |
| AI analysis | your Anthropic key | managed credits |
| External threat (DRP) feeds | — | operated subscriptions |
| SOC log ingestion + AI triage | — | metered |
| Team workspaces · RBAC · support | — | ✓ |
| Upgrades and backups | you | us |
When something is wrong
The failures we see most, and what each one actually means.
Both sign-in buttons are disabled and there is no local form.
SELFHOST_ADMIN_EMAIL, SELFHOST_ADMIN_PASSWORD and SELFHOST_ADMIN_TOTP_SECRET must be set in every mode. Configure all three, restart the API, and check `curl localhost:6040/health`: `localLogin` enables the form and `localMfa` tells it to show the authenticator field.
docker compose up fails immediately on a variable.
The compose file refuses to start rather than boot an instance nobody can sign into or whose sessions are signed with a default secret. The error names the missing variable; set it in .env.
The API logs a MongoDB transaction or replica-set error.
The database is running standalone. The bundled mongo-init container initialises rs0 on first start — if you pointed MONGO_URL at your own MongoDB, it has to be a replica set too.
AI summaries look generic and say the analysis is heuristic.
That is the honest fallback with no ANTHROPIC_API_KEY, or when a call failed. Set the key, restart api and worker, and the label disappears.
Port 6000 or 6040 is already in use.
Something else holds the port. Change the published port in compose.yml, and set PUBLIC_WEB_URL / PUBLIC_API_URL to match so cookies and CORS still line up.
Scans queue but never progress.
The worker is not running or cannot reach Redis. `docker compose ps` shows its state and `docker compose logs worker` shows the connection it is failing.
The licence, in plain terms
heimdallX is licensed under the GNU Affero General Public License v3.0. Read it in full in the repository — this is the short version, not a substitute.
- Run it, for anything, including commercially, with no fee and no asset limit.
- Read and modify every line, including the checks the scanner runs.
- If you offer a modified version to others over a network, publish your modifications under the same licence.
- Using it internally, unmodified, asks nothing of you.
Start with the repository.
Everything on this page is in it. If you would rather not operate it, the hosted service runs the same platform.