Self-hosting
Straden is deployed with Docker Compose from a single image, ghcr.io/kwasii1/straden. Every setting is optional — the defaults give you a working local instance.
Quick start (local)
BASHmkdir straden && cd straden curl -fsSLO https://raw.githubusercontent.com/kwasii1/straden/main/deploy/compose.yaml curl -fsSL https://raw.githubusercontent.com/kwasii1/straden/main/deploy/.env.example -o .env docker compose up -d
Open http://localhost:8000 and create the admin account in the setup wizard.
Services
| Service | What it does |
|---|---|
app | Web UI + MCP server (FrankenPHP/Caddy). Proxies websockets to Reverb and handles HTTPS. Runs database migrations on start. |
reverb | Websocket server for live run progress, chat streaming and notifications. |
runner | Queue worker (Horizon) that executes k6 load tests. Ships with k6 v2.0.0. |
scheduler | Checks running tests and finalises them. |
postgres | Application database. |
redis | Cache, sessions and queues. |
influxdb | InfluxDB 1.8 — k6 time-series metrics. |
The reverb, runner and scheduler services wait for app to become healthy, so secrets and migrations are always in place before they start.
Running on a VPS with HTTPS
-
Point a DNS record (e.g.
straden.example.com) at the server. -
In
.env:DOTENVSTRADEN_DOMAIN=straden.example.com APP_URL=https://straden.example.com HTTP_PORT=80 HTTPS_PORT=443 DB_PASSWORD=change-me-before-first-start -
docker compose up -d
Caddy obtains and renews the Let's Encrypt certificate automatically. Websockets use the same origin (wss://straden.example.com/app/...), so there is no extra port to open.
Behind your own reverse proxy
If nginx, Traefik or a load balancer already terminates TLS, keep STRADEN_DOMAIN=:80, set APP_URL to the public HTTPS URL, and proxy everything — including websocket upgrades on /app/* — to the app container's port 80.
Environment reference
| Variable | Default | Description |
|---|---|---|
STRADEN_DOMAIN | :80 | :80 serves plain HTTP. A hostname enables automatic HTTPS. |
APP_URL | http://localhost:8000 | Public URL, used in links and emails. |
HTTP_PORT / HTTPS_PORT | 8000 / 8443 | Host ports mapped to the app container. |
STRADEN_VERSION | latest | Image tag. Pin it in production. |
STRADEN_ADMIN_NAME / _EMAIL / _PASSWORD | — | Create the first admin without the browser. Only used while there are no users. |
DB_PASSWORD | straden | Postgres password. Set before the first start. |
APP_KEY, REVERB_APP_ID, REVERB_APP_KEY, REVERB_APP_SECRET | generated | Override the auto-generated secrets. |
MAIL_* | — | SMTP settings for password reset and email verification. In-app notifications don't need mail. |
LOG_LEVEL | info | Log verbosity (logs go to stderr). |
Admin accounts
There is no public registration. The admin adds users under Settings → Users.
To create the admin non-interactively, set STRADEN_ADMIN_EMAIL and STRADEN_ADMIN_PASSWORD before the first start. Locked out? Create or promote an admin and reset their password from the CLI:
BASHdocker compose exec app php artisan straden:admin you@example.com
A random password is printed unless you pass --password=. See Users & Security for managing accounts.
Secrets
APP_KEY and the Reverb credentials are generated on first start and stored in the storage volume at storage/app/.straden-secrets. AI provider keys are encrypted with APP_KEY — lose it and they become unreadable, so back this volume up.
Upgrading
BASHdocker compose pull docker compose up -d
Migrations run automatically. Pin STRADEN_VERSION (e.g. 1.2.0) in production and bump it deliberately.
Backups
BASH# Database docker compose exec postgres pg_dump -U straden straden > straden.sql # Scripts, run logs, cloned repositories and secrets docker run --rm -v straden_storage:/data -v "$PWD":/backup alpine tar czf /backup/storage.tgz -C /data . # Metrics docker run --rm -v straden_influxdb:/data -v "$PWD":/backup alpine tar czf /backup/influxdb.tgz -C /data .
Load generator capacity
k6 runs inside the runner container, so the host's CPU and network bandwidth limit how much load you can generate. On a shared host, uncomment the deploy.resources block in compose.yaml:
YAMLrunner: deploy: resources: limits: { cpus: "2", memory: 2g }
For the most accurate results, run Straden on a different machine from the system under test.
Building the image yourself
BASHgit clone https://github.com/kwasii1/straden && cd straden/deploy docker compose -f compose.yaml -f compose.build.yaml up -d --build