Self-hosting
Self-Hosting Configuration Reference
The environment variables a self-hosted AnyReplay deployment reads for the API, ingest and dashboard, including build-time values and limits you can tune.
Each service reads its own environment file — for example /etc/anyreplay/api.env, ingest.env and web.env — rather than sharing one, because ingest faces the public internet and should hold nothing the others need. Variables already set in the environment take precedence over the file. Never commit real values; the ones below are names and defaults only.
API
| Variable | Default | Purpose |
|---|---|---|
NODE_ENV | development | Set to production. |
API_PORT | 4000 | Listening port. |
WEB_ORIGIN | http://localhost:3000 | The dashboard origin(s), comma-separated, allowed to call the API. |
JWT_ACCESS_SECRET, JWT_REFRESH_SECRET | — | Two separate random secrets of at least 32 characters (openssl rand -hex 32). The API refuses to start with a shorter one. |
DATABASE_URL | — | PostgreSQL connection string. Required in production; the API refuses to start without it. |
STORAGE_DIR | ./.dev-storage | Where recordings live on the filesystem. Must be the same location for the API and ingest. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI | — | Google sign-in. Absent means the button is not offered. |
RATE_LIMIT_PER_MINUTE | 600 | Requests per minute per source address. 0 disables it. |
RATE_LIMIT_AUTH_PER_MINUTE | 20 | Sign-in, sign-up and invitation attempts per minute. |
RATE_LIMIT_REFRESH_PER_MINUTE | 240 | Session refreshes per minute per source address. Every page load in every open tab uses one, and an office behind one address shares the budget. |
REFRESH_REUSE_GRACE_SECONDS | 30 | How long a refresh token that has just been rotated still answers with the replacement it was rotated into, rather than being treated as a replay. Covers tabs waking together and a laptop resuming. 0 restores strict single-use, at the cost of signing people out for races they cannot avoid. |
RETENTION_SWEEP_INTERVAL_MS | 21600000 | How often expired recordings are deleted (six hours). 0 keeps everything for ever. |
RETENTION_BATCH_SIZE | 200 | Sessions deleted per sweep batch. |
PRIVACY_JOB_INTERVAL_MS | 30000 | How often pending privacy requests are picked up and their recordings deleted. 0 disables the worker, and requests stay pending. |
CLOSURE_GRACE_DAYS | 30 | Days between an owner closing an organisation and its data being deleted. An owner can cancel the closure until then. |
CLOSURE_SWEEP_INTERVAL_MS | 3600000 | How often organisations past their grace period are deleted (hourly). 0 disables it, and closed organisations are kept. |
TRANSLATION_PROVIDER, TRANSLATION_API_KEY, TRANSLATION_MODEL | openai | The model that translates recorded sessions: openai (the default, gpt-5-nano), anthropic, fal, or none. Only the key is needed for the default; OPENAI_API_KEY is accepted in its place. Without a key the player says translation is not configured. |
TRANSLATION_REASONING_EFFORT | minimal | Reasoning budget for models that have one. Translating an interface string needs none, and reasoning tokens are billed as output. off omits the parameter. |
SENTRY_DSN, SENTRY_TRACES_SAMPLE_RATE | Off | Error reporting. Request bodies, cookies, visitor IPs and recordings are never sent. |
EMAIL_PROVIDER, EMAIL_FROM, EMAIL_REGION | none | Outgoing mail, for example through Amazon SES (ses). With none, messages are written to the service log instead of sent. |
SALES_EMAIL, USAGE_EMAIL_SIGNER, USAGE_NOTIFY_INTERVAL_MS | — | Who usage emails come from and reply to, and how often usage is checked (15 minutes). |
ONBOARDING_EMAIL_INTERVAL_MS | 600000 | How often the API looks for someone to welcome or an organisation due a setup tip (10 minutes). 0 turns the welcome and the tips off. |
Ingest
| Variable | Default | Purpose |
|---|---|---|
INGEST_PORT | 4001 | Listening port. |
DATABASE_URL | — | The same database as the API. Required in production. |
STORAGE_DIR | ./.storage | The same location as the API. |
MAX_CHUNK_BYTES | 524288 | Largest chunk accepted (512 KB); larger ones get 413. |
RATE_LIMIT_PER_VISITOR_PER_MINUTE | 120 | Requests per minute from one device: project key and visitor id, or key and address for identify and assets. One recorder sends 12–30. |
RATE_LIMIT_PER_KEY_PER_MINUTE | 12000 | Ceiling for one project key across all its devices. |
RATE_LIMIT_PER_IP_PER_MINUTE | 6000 | Ceiling for one source address. High, because an office or a mobile carrier puts many devices behind one address. |
RATE_LIMIT_MAX_TRACKED_KEYS | 100000 | Most keys each limiter remembers; the least recently seen is forgotten first. |
MAX_ASSET_BYTES | 3145728 | Largest app image accepted (3 MB): from the React Native SDK, or from an app built from web pages, which uploads the images it ships. |
MAX_ASSETS_PER_PROJECT | 500 | Distinct images one project may store. |
MAX_ASSET_STORAGE_BYTES | 209715200 | Total image storage per project (200 MB). |
PROJECT_CACHE_TTL_MS | 60000 | How long a project's key, status and allowed domains are cached. |
PROJECT_REVALIDATE_MS | 5000 | How soon a project is re-read when ingest is about to refuse a request over its domains or apps, so a domain or app allowed from the dashboard is let in within seconds. Also caps how often a refused project can reach the database. |
ORG_QUOTA_CACHE_TTL_MS | 15000 | How long plan limits and counters are cached, so an upgrade takes effect quickly. |
SENTRY_DSN | Off | Error reporting for ingest. |
Dashboard
| Variable | When read | Purpose |
|---|---|---|
PORT | Run time | The port next start listens on. |
NEXT_PUBLIC_API_URL | Build time | Your API's public URL. |
NEXT_PUBLIC_INGEST_URL | Build time | Your ingest URL. Used in generated snippets. |
NEXT_PUBLIC_CDN_URL | Build time | Where the recorder bundle is served, including /v1. Used in generated snippets. |
Configurations that refuse to start
- A JWT secret shorter than 32 characters.
NODE_ENV=productionwith noDATABASE_URL. Without it the services would use an embedded, single-process database that two services cannot share safely.- An unsupported Node.js version.
A service that refuses to boot says why in its log. For what else to check, see the overview.