Skip to main content
You do not need a server to scan. This guide is for running the hosted half: the dashboard, the /api/v1 JSON API, the share viewer, and the retention worker.

Prerequisites

Local setup

1

Install and create databases

2

Configure

Two values must be set before anything starts:
Generate a key:
Configuration is read from the process environment; a repository-root .env is loaded as a fallback in development only. In production a missing variable fails fast with an actionable message rather than silently resolving to whatever is on disk.
3

Build and migrate

Migrations are generated SQL, not a runtime schema diff.
4

Run

The worker also has a single-pass mode, which is what you want when watching retention behaviour rather than waiting for an interval:
5

Point the CLI at it

Required configuration

Everything else has a default. The full list, with what each one changes, is in Environment variables.

Object storage

For a deployment, use S3-compatible storage:
s3 requires the endpoint, bucket, access key, and secret key together.
The filesystem driver is refused under NODE_ENV=production, because report bodies would live on one container’s disk — lost on the next deploy and invisible to other replicas. Set OBJECT_STORAGE_ALLOW_EPHEMERAL=true only for a throwaway production-mode run such as the end-to-end suite.

Behind a proxy

Login and device-link throttles read the client address that many entries in from the right of X-Forwarded-For. Leave it unset when nothing proxies the app. The reason is worth understanding: the leftmost X-Forwarded-For entry is whatever the caller typed, so keying a throttle on it lets an attacker mint a fresh bucket per request and the throttle stops existing. Only entries your own proxies added can be trusted. Railway and most single-proxy platforms need 1. If a trusted edge sets a real-client-address header, name it:

Multiple replicas

Without REDIS_URL, rate limits are process-local counters — fine for a single process, but N replicas allow N times each limit. EVENT_BUS_DRIVER=postgres uses LISTEN/NOTIFY and works across instances; memory is single-process only.

Platform administrator

Exactly the configured address is promoted, and only when that address signs up. Nobody is promoted implicitly, and a deployment with this unset has no administrator at all.
The repository’s README.md and docs/LOCAL-WALKTHROUGH.md state that the first account in a fresh deployment becomes the platform administrator. The implementation in apps/web/lib/auth-service.ts does not do that — only PLATFORM_ADMIN_EMAIL grants the role. Trust the code.

Health and readiness

The split is deliberate. A database outage must not put every replica into a restart loop, and “the process is up” is not a usable signal on its own — a replica that cannot reach Postgres serves 500s for every real operation while its marketing pages still return 200. Both endpoints answer on any hostname, because redirecting a health check would make a monitor report the redirect target’s health instead.

Container builds

Two Dockerfiles at the repository root, both building and running as non-root: railway.web.json and railway.worker.json carry deploy configuration: the Dockerfile paths, a pre-deploy migration command, /api/v1/health as the health check, region europe-west4, and drain and overlap windows so a deploy does not cut live streams. scripts/railway-provision.sh creates the project, adds PostgreSQL and Redis, generates REPORT_ENCRYPTION_KEY, and prints the short list of values it deliberately will not invent. It is idempotent and refuses to change a project it did not create.

Canonical hostnames

apps/web/proxy.ts handles the three hostnames the product is intended to answer on. None of them is deployed today, so this matters only once you put a deployment behind real DNS — and if you self-host under your own domain, edit packages/brand/src/index.ts, which is the single source of these strings. Non-API paths are redirected to the canonical host with a 308, which preserves the method and body — a 302 would silently turn a POST into a GET. API paths are deliberately not redirected off the API host.

Payments (optional)

All four are required together or the application refuses to start: a half-configured payment path takes money and grants nothing. Left unset, the deployment sells nothing — the pricing page still renders and says checkout is unavailable. A sandbox token cannot charge anyone. Point the provider’s webhook at POST /api/v1/billing/webhook; the raw body is verified byte-for-byte before anything is parsed, the delivery id is claimed before the plan changes so a retry cannot apply an upgrade twice, and the plan is resolved from the product id through your own configuration rather than from the request.

Verification suites

Database tests and test:e2e use TEST_DATABASE_URL, default postgresql://localhost:5432/myrqen_test.

Demonstration data

Nothing runs these for you — no import, no lifecycle script, no deploy step. demo:seed refuses a non-loopback or production database unless MYRQEN_DEMO_ALLOW_REMOTE=1 is set on that command line, and the refusal says so. Every row it writes carries is_demo and the run’s demo_seed_id, and demo:purge deletes nothing else. The demonstration accounts cannot sign in; the workspace is meant to be viewed through the share links the seeder prints.

Signed releases

Serve the signed manifest by pointing UPDATE_MANIFEST_PATH at it; clients fetch it from GET /api/v1/updates/manifest. Pin the public keys clients must verify against in UPDATE_PUBLIC_KEYS as {"keyId":"-----BEGIN PUBLIC KEY-----…"}. Clients verify the ed25519 signature against the pinned key, check the artifact digest and size, and refuse downgrades, stale manifests, and revoked versions before anything is written to disk. The server never signs on demand and never returns executable content.