AGPL-3.0 · self-hosted

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.

View the repositoryCompare with Cloud

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.

  1. 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.git
    cd heimdallx
    cp .env.example .env
  2. Generate 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_SECRET
    openssl rand -base64 24 # → SELFHOST_ADMIN_PASSWORD
  3. Set 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 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.

  4. 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 -d
    docker compose ps
  5. Check 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":{…}}
  6. Sign in and run a scan

    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/

Configuration

The variables that matter for a self-hosted install. Everything missing simply disables that feature — the platform boots without any external credentials.

SESSION_SECRETrequired

Signing key for session tokens. 32 random bytes; rotating it logs everyone out.

SELFHOST_ADMIN_EMAILrequired

The local administrator account. Enables POST /auth/local together with the password below.

SELFHOST_ADMIN_PASSWORDrequired

Password for that account, 12 characters minimum. Compared in constant time; rate-limited to 10 attempts per 5 minutes per IP.

MONGO_URLoptional

Defaults to the bundled replica set. Point it at your own cluster or an Atlas SRV URI — the connection-string shape is identical.

REDIS_URLoptional

Job queue and realtime fan-out. Defaults to the bundled Redis.

ANTHROPIC_API_KEYoptional

Turns 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_FASToptional

Model per workload. FAST carries the interactive volume and should stay a Haiku-class model.

PUBLIC_WEB_URL / PUBLIC_API_URLoptional

Set both when you expose the instance on a domain — they drive CORS, cookies and OAuth redirects.

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECREToptional

Adds Google sign-in for the rest of your team. Apple sign-in has its own key trio.

HIBP_API_KEY / GITHUB_TOKENoptional

Breach lookups and higher GitHub rate limits for the exposure and code modules.

ENABLE_PORT_SCANoptional

Allows active port scanning. Off by default and consent-gated — only turn it on for assets you are authorised to probe.

STORAGE_DRIVER / S3_*optional

Where 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 pull
docker compose build
docker 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.gz
docker 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 worker
curl -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 CloudSelf-hostedCloud
Scan engine · dashboard · worker
Findings · fix-first queue · assets · attack surface
EASM discovery · breach simulation · compliance
Integrations + CI gate (SARIF)
AI analysisyour Anthropic keymanaged credits
External threat (DRP) feedsoperated subscriptions
SOC log ingestion + AI triagemetered
Team workspaces · RBAC · support
Upgrades and backupsyouus

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 password form.

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.

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.

AGPL-3.0 →

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.

Self-host heimdallX — heimdallX