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.
The reasons operators actually give us — each is a property of the deployment, not a plan tier.
Scan targets, findings and reports are written to a database you run. There is no phone-home, no telemetry beacon and no license check.
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.
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.
Self-hosted has no seat count and no asset cap. What you can scan is bounded by the hardware you give it.
A single host is enough to start. The worker is the part that wants CPU — scans are the workload.
MongoDB must be a replica set — the app uses transactions. The bundled compose file initialises a single-node one for you.
Six steps. Copy them in order; nothing here is a placeholder except the values you choose.
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 .env
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_PASSWORD
Open .env and fill these in. A self-hosted instance runs in production mode, which disables the development login — the admin email and password 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>
The password must be at least 12 characters: /auth/local is reachable from wherever you expose the instance.
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 ps
/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":{…}}
Open the dashboard and sign in with the admin email and password from step 3. Enter a domain you own and watch the pipeline stream live.
open http://localhost:6000/app/
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 password below.
SELFHOST_ADMIN_PASSWORDrequiredPassword for that account, 12 characters minimum. Compared in constant time; rate-limited to 10 attempts per 5 minutes per IP.
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.
The four things you will actually do after the install.
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
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
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
Scans are queued jobs, so throughput is a worker count. Add workers before you add API instances.
docker compose up -d --scale worker=3
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.
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 |
The failures we see most, and what each one actually means.
SELFHOST_ADMIN_EMAIL and SELFHOST_ADMIN_PASSWORD are not both set, so the server reports local login as unavailable and the dashboard hides the form. Set them and restart the API — `curl localhost:6040/health` shows `localLogin` as the deciding flag.
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 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.
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.
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.
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.
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.
Everything on this page is in it. If you would rather not operate it, the hosted service runs the same platform.