Operations

Self-hosting

Run the whole platform inside your own boundary.

Rokkhe self-hosts on infrastructure you operate. The shape we run is the shape you can run — a single VM with Compose behind a reverse proxy. Nothing in the stack calls home, and the only outbound traffic is what you configure: your model provider, and whichever integrations you connect.

#What you need

  • A Linux host with Docker and the Compose plugin. One VM is enough; the reference deployment runs every service on a single machine.
  • A domain pointed at the host if you want Caddy to obtain certificates automatically.
  • Credentials for a model provider. Rokkhe never ships a key; the deployment uses yours.
  • Outbound access to your image registry, or a mirror you populate yourself.

#The stack

Nine services come up together. Only Caddy publishes a port — everything else is reachable only on the internal networks below.

ServiceImageRole
caddycaddy:2-alpineTLS termination and the only published port.
webrokkhe-webThe console and public site.
apirokkhe-apiApplication API. Depends on migrate and redis.
authrokkhe-authSessions and OAuth. Depends on the database and redis.
workerrokkhe-scan-workerRuns engagements and creates each scan’s sandbox.
timescaledbtimescale/timescaledb:latest-pg16Findings, cases, evidence, and run history.
redisredis:7-alpineQueues and short-lived state.
migratetimescale/timescaledb:latest-pg16Applies migrations once, then exits.
egress-gatewaybuilt locallyOptional. Constrains what a sandbox may reach.

#Network boundaries

The stack is segmented so that compromising one tier does not reach the next. This is the part worth reading if you are assessing Rokkhe rather than deploying it.

NetworkMembersReachable from the internet
edgecaddy, web, api, authThrough Caddy only.
datatimescaledb, redis, migrate, api, auth, workerNo. The database has no edge route at all.
sandboxworker, and each scan’s containerNo, and it carries no route to data.

The worker sits on both data and sandbox because it owns the boundary between them: it reads work from the queue and creates the container that executes against a target. A scan’s container never joins the data network, so a compromised target cannot reach your findings.

#Required configuration

Configuration lives in infra/.env. The base Compose file carries development defaults so a local stack comes up unattended; the production overlay removes them. These four have no default in production and the stack refuses to render without them.

VariableMeaning
POSTGRES_PASSWORDDatabase password. The development default is refused.
DATABASE_URLConnection string for the TimescaleDB instance.
PUBLIC_ORIGINThe HTTPS origin the deployment answers on. Also becomes the site URL the web app renders.
AUTH_SECRETAt least 32 characters. The development placeholder is refused.
The base file is not production

docker-compose.yml alone brings the stack up with AUTH_SECRET set to a known placeholder, POSTGRES_PASSWORD set to rokkhe, and ROKKHE_ENV left at development. That is deliberate, so a local stack needs no setup — but it means running the base file alone on a reachable host publishes a deployment with published credentials. Always pass both files.

#Commonly set

VariableDefaultMeaning
SITE_ADDRESS:8080Set to your domain for automatic HTTPS.
IMAGE_OWNER / IMAGE_TAGmainWhich published images to pull.
ROKKHE_IMAGErokkhe-sandbox:latestThe sandbox image each scan runs in.
SELF_SERVE_SCANS1Whether accounts may start their own scans.
CADDY_LOCAL_BIND127.0.0.1:8080Loopback publish for the base stack.
ROKKHE_CREDENTIAL_KEYRINGunsetEncryption keyring for stored integration credentials.

#Bringing it up

Both files, every time. The overlay is what pins ROKKHE_ENV to production, requires the four variables above, and moves Caddy onto 80 and 443.

bashcd infra
cp .env.example .env      # then fill in the four required values

docker compose \
  -f docker-compose.yml \
  -f docker-compose.prod.yml \
  pull

docker compose \
  -f docker-compose.yml \
  -f docker-compose.prod.yml \
  up -d

migrate runs to completion before api and worker start, so the first boot applies the schema before anything serves traffic.

#Health

  • /health/live — the process is alive.
  • /health/startup — the backend initialized at least once.
  • /health/ready — required dependencies are healthy; the edge gates traffic on this.
  • /api/health — public compatibility endpoint reporting only status and readiness.

The detailed probes are internal: the edge answers 404 for /health/* because the payload names versions, environment, and dependency state. Compose healthchecks cover timescaledb, redis, api, auth, and web, so a failed dependency holds back the services that need it rather than surfacing as a broken page.

#Upgrading

bashdocker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Recreating the worker cancels running scans

An upgrade replaces the worker container, and a scan in progress does not survive it — its working directory goes with the container. Drain running engagements before deploying, or expect to restart them.

#State and backups

VolumeHoldsBack up
pgdataFindings, cases, evidence, run history, accounts.Yes — this is the deployment.
caddy_dataIssued certificates.Optional; re-issues on loss.
caddy_configCaddy runtime state.No.
rokkhe_configEngine configuration written at runtime.Optional.

pgdata is the only volume whose loss is unrecoverable. Snapshot it with your normal database tooling rather than copying the directory from under a running Postgres.