Skip to main content

Server Configuration

All configuration is done via environment variables. Copy .env.example to .env and adjust for your setup.


Core

VariableDefaultDescription
PORT3010Port the server listens on
APP_ENVdevelopmentEnvironment mode. Set to production for live deployments — enables SSL for PostgreSQL and tightens security defaults
DB_TYPEsqliteDatabase backend: sqlite (default, self-hosted) or postgres (production/scaled)
DATABASE_URLPostgreSQL connection string. Required when DB_TYPE=postgres. Example: postgresql://user:pass@host:5432/db
LOG_LEVELinfoZerolog log level: debug, info, warn, error
DATA_DIRdataDirectory for persistent data (SQLite DB, browser logs). Resolved to an absolute path at startup
SQLITE_PATH<DATA_DIR>/relay.dbOverride the SQLite file path. Only used when DB_TYPE=sqlite

Auth & Registration

VariableDefaultDescription
ADMIN_EMAILIf set, creates this admin account on startup if it doesn't exist
ADMIN_PASSWORDPassword for the auto-created admin account
DISABLE_REGISTRATIONfalseWhen set, prevents new user self-registration
CREDENTIALS_ENCRYPTION_KEYAES-256 key for encrypting Foundry credentials stored on scoped API keys. Required for all backends. Generate: openssl rand -hex 32
RETURN_RESET_TOKENfalseTesting only. Returns the raw password reset token in the API response instead of emailing it. Never enable in production

Admin Dashboard

VariableDefaultDescription
ADMIN_JWT_SECRETJWT signing secret for admin sessions. Required for all deployments (sqlite and postgres). Generate: openssl rand -base64 32
ADMIN_ALLOWED_IPS— (allow all)Comma-separated IP allowlist for all /admin routes. Example: 127.0.0.1,10.0.0.5
ADMIN_INTERNAL_PORT0 (disabled)If set, serves the admin panel on a separate internal port (useful for Fly.io private networking)
ADMIN_SESSION_MAX_HOURS8Maximum admin session duration in hours. In dev mode the full window is used as the JWT TTL — no re-login needed within this window
ADMIN_AUDIT_LOG_RETENTION_DAYS90How many days to keep admin audit log entries
ADMIN_LOGIN_RATE_LIMIT10Max admin login attempts per IP per 15-minute window
ADMIN_LOCKOUT_THRESHOLD5Failed login attempts before account lockout
ADMIN_LOCKOUT_MINUTES15How long (minutes) a locked account stays locked
ADMIN_SECURE_COOKIESfalseSet to true to enable the Secure cookie flag and __Host- prefix for the admin session cookie. Only works when the admin panel is served over HTTPS. Default false so plain HTTP self-hosted deployments work out of the box

API Usage Limits

Self-hosted deployments

The Docker image defaults the monthly limit to 0 (unlimited). You only need to set this if you want to enforce a quota on your own instance. Subscription UI (plan badge, upgrade button) is automatically hidden when STRIPE_SECRET_KEY is not set — no extra config required.

VariableDefault (Docker)Description
MONTHLY_REQUEST_LIMIT0 (unlimited)Monthly request quota for free-tier users. 0 = unlimited. Paid subscribers (Stripe active) are always unlimited. The commercial hosted relay uses 5000

Rate Limiting

All rate limiters are per-IP unless noted.

VariableDefaultWindowApplies to
PER_MINUTE_REQUEST_LIMIT3001 minPer-API-key burst limit on all API endpoints. Set to 0 to disable
AUTH_RATE_LIMIT515 minRegister, login, regenerate-key endpoints
PASSWORD_RESET_RATE_LIMIT31 hourForgot-password / reset-password endpoints
ACCOUNT_MGMT_RATE_LIMIT101 hourChange-password, delete-account, export-data endpoints
PAIRING_RATE_LIMIT1015 minRelay-to-Foundry pairing attempts
KEY_REQUEST_RATE_LIMIT2015 minScoped key approval flow submissions
PROBE_RATE_LIMIT601 minActive-connection probe requests

Redis

Redis is optional. When enabled it allows multiple relay instances to route requests to the correct Foundry client across instances. Automatically disabled when DB_TYPE=sqlite.

VariableDefaultDescription
REDIS_URLRedis connection URL. Example: redis://default:password@host:6379
REDIS_ENABLEDtrueSet to false to disable Redis even when REDIS_URL is set

Email (Password Reset & Notifications)

If SMTP_HOST is not configured, password reset tokens are logged to the console instead of emailed.

VariableDefaultDescription
SMTP_HOSTSMTP server hostname
SMTP_PORT587SMTP port
SMTP_USERSMTP username
SMTP_PASSSMTP password
SMTP_FROM[email protected]From address for outgoing emails
SMTP_SECUREfalseUse TLS for SMTP connection

Stripe (Subscription Billing)

Stripe is fully disabled when STRIPE_SECRET_KEY is not set.

VariableDefaultDescription
STRIPE_SECRET_KEYStripe API secret key (sk_test_... for dev, sk_live_... for production)
STRIPE_WEBHOOK_SECRETStripe webhook signing secret (whsec_...)
STRIPE_PRICE_IDStripe Price ID for the subscription product
STRIPE_PORTAL_URLStripe Customer Portal URL for self-service subscription management
FRONTEND_URLhttps://foundryrestapi.comBase URL for Stripe checkout redirect URLs

WebSocket & Connection Tuning

VariableDefaultDescription
WEBSOCKET_PING_INTERVAL_MS20000Interval (ms) for WebSocket keepalive pings
CLIENT_CLEANUP_INTERVAL_MS15000Interval (ms) to check for and remove dead clients
AUTH_CACHE_TTL_SECONDS30How long to cache API key lookups in memory to reduce DB load

Headless Sessions

Headless sessions use Chromium to automate Foundry logins.

VariableDefaultDescription
ALLOW_HEADLESStrueSet to false to completely disable headless session creation. Note: headless sessions are disabled on the public hosted relay — self-host to use this feature
MAX_HEADLESS_SESSIONS0Max concurrent headless browser sessions per instance. 0 means no limit. When Redis is configured, this cap is enforced globally across all relay instances
HEADLESS_SESSION_TIMEOUT600Seconds of inactivity before a headless session is automatically stopped. Set to 0 to never time out due to inactivity
MAX_INTERACTIVE_SESSIONS_PER_KEY3Max interactive sheet sessions per scoped API key
PUPPETEER_EXECUTABLE_PATHauto-detectedPath to Chrome/Chromium executable
CAPTURE_BROWSER_CONSOLE— (disabled)Log level for browser console output: error, warn, or debug. Logs saved to <DATA_DIR>/browser-logs/
REQUIRE_PAID_HEADLESSfalseRequire an active paid subscription to create headless sessions
CHROME_USER_DATA_DIR<DATA_DIR>/chrome-profilePersistent Chrome profile directory. Enables V8 bytecode cache and HTTP cache across restarts — significantly reduces warm-session JS execution time
CHROME_JS_HEAP_MB2048Max V8 old-space heap in MB. Increase if running heavy module loadouts to reduce GC pause spikes
CHROME_WINDOW_WIDTH1280Headless browser viewport width in pixels
CHROME_WINDOW_HEIGHT800Headless browser viewport height in pixels. Must be ≥768 (Foundry minimum). Also affects canvas/entity screenshot resolution
CHROME_ENABLE_SHMfalseAllow Chrome to use /dev/shm for IPC. Enable when the host/container has ≥256 MB of shared memory (--shm-size=256m in Docker)
CHROME_GPU_MODEautoChrome rendering backend. auto detects the best available option. nvidia = NVIDIA hardware acceleration via ANGLE Vulkan + native Vulkan ICD (requires nvidia-container-toolkit). gpu = Intel/AMD GPU via Mesa/DRI (Ozone headless + ANGLE GL — no display server required). xvfb = Mesa software rendering via Xvfb. swiftshader = software WebGL bundled in Chrome (always works, used as fallback). See GPU acceleration in Docker below.

Fly.io / Infrastructure

These are typically set automatically by Fly.io and do not need to be configured manually.

VariableDefaultDescription
FLY_ALLOC_IDlocalInstance identifier for multi-instance request routing
FLY_INTERNAL_PORTInternal port on the Fly.io private network
APP_NAMEApp name for Fly.io private networking

Request Body Limits

VariableDefaultDescription
MAX_JSON_BODY_SIZE_MB250Maximum request body size in MB for API calls. Applies globally
MAX_UPLOAD_SIZE_MB250Maximum file upload size in MB. Applies to file upload endpoints

Deprecated / Legacy Names

These names still work as fallbacks but should not be used in new deployments.

Old nameCurrent name
FREE_API_REQUESTS_LIMITMONTHLY_REQUEST_LIMIT
NODE_ENVAPP_ENV
HEADLESS_INACTIVE_TIMEOUTHEADLESS_SESSION_TIMEOUT

Documentation Site

These variables are only needed when building or deploying the Docusaurus documentation site (docs/). They are not used by the relay server.

When both ALGOLIA_APP_ID and ALGOLIA_SEARCH_API_KEY are set, the docs site enables Algolia DocSearch. If either is absent, the search widget is omitted from the build.

VariableDefaultDescription
ALGOLIA_APP_IDAlgolia application ID. Required to enable doc search
ALGOLIA_SEARCH_API_KEYAlgolia search-only API key (public, safe to expose in the browser)
ALGOLIA_INDEX_NAMEfoundryvtt-rest-api-relayAlgolia index name to query

Docker Configuration

Two compose files are included in the repository:

  • docker-compose.local.yml — SQLite, no billing, unlimited requests. The recommended starting point for self-hosted setups.
  • docker-compose.postgres.yml — Postgres + optional Redis. For self-hosted setups that prefer Postgres or need horizontal scaling.
# docker-compose.local.yml (self-hosted, SQLite)
# Copy .env.example to .env and fill in ADMIN_EMAIL, ADMIN_PASSWORD, etc.
services:
relay:
image: threehats/foundryvtt-rest-api-relay:latest
container_name: foundryvtt-rest-api-relay
env_file: .env
environment:
- PORT=3010
- DB_TYPE=sqlite
- APP_ENV=production
volumes:
- ./data:/app/data
ports:
- "3010:3010"
restart: unless-stopped

Start it with:

docker compose -f docker-compose.local.yml up -d

GPU Acceleration in Docker

GPU acceleration significantly speeds up all headless browser operations and reduces CPU load. Foundry's renderer (Pixi.js) is heavily GPU-dependent — without hardware acceleration it runs on the CPU, which is expensive. GPU mode is auto-detected — no configuration needed beyond exposing the device.

Host OS support
  • Linux (Docker Engine) — full GPU support for Intel, AMD, and NVIDIA.
  • Linux (Docker Desktop) — GPU passthrough is not supported. Docker Desktop on Linux runs containers inside a LinuxKit VM that cannot access the host GPU. Use Docker Engine (CE) for GPU support on Linux.
  • Windows — NVIDIA GPU supported via Docker Desktop with the WSL2 backend (see Windows setup below). Intel/AMD via DRI is unreliable in WSL2.
  • Mac — GPU passthrough is not available. Docker on Mac runs containers inside a Linux VM with no access to Metal or the host GPU. The relay falls back to SwiftShader automatically.

Intel / AMD (Linux)

Expose the DRI device to the container. The relay's entrypoint script automatically resolves the host render group GID so no group_add is needed.

services:
relay:
# ... your existing config ...
devices:
- /dev/dri:/dev/dri
shm_size: "256m"
environment:
- CHROME_ENABLE_SHM=true

NVIDIA (Linux)

First, install nvidia-container-toolkit on the host.

Two configuration options — pick the one that fits your setup:

Configure nvidia as the default Docker runtime, then use environment variables to expose the GPU:

sudo nvidia-ctk runtime configure --runtime=docker --set-as-default
sudo systemctl restart docker

The relay image already sets NVIDIA_VISIBLE_DEVICES=all and NVIDIA_DRIVER_CAPABILITIES=graphics,utility via ENV in the Dockerfile — no compose variables required. Just enable shared memory:

services:
relay:
# ... your existing config ...
shm_size: "256m"
environment:
- CHROME_ENABLE_SHM=true

CHROME_GPU_MODE=auto (default) detects the NVIDIA GPU automatically. Chrome uses ANGLE Vulkan with the native NVIDIA Vulkan ICD for hardware-accelerated WebGL.

Option B — CDI (no default runtime change)

Generate a CDI spec and expose the device directly — no default-runtime change required:

sudo mkdir -p /etc/cdi
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
sudo systemctl restart docker

Add to the relay service:

services:
relay:
# ... your existing config ...
devices:
- "nvidia.com/gpu=all"
shm_size: "256m"
environment:
- CHROME_ENABLE_SHM=true

CHROME_GPU_MODE=auto detects the GPU automatically once it is exposed — no additional relay configuration needed.

Windows (WSL2)

Docker Desktop on Windows uses a WSL2 backend that supports NVIDIA GPU passthrough. The compose configuration is identical to Linux — the toolkit runs inside WSL2.

One-time setup:

  1. Install an NVIDIA driver on Windows (version 471.11 or later). The WSL2 GPU driver is bundled — do not install a separate CUDA driver inside WSL2.
  2. Inside your WSL2 distribution, install nvidia-container-toolkit following the Linux instructions.
  3. Restart Docker Desktop.

After that, run the same two nvidia-ctk + restart docker commands as Linux above inside WSL2, and use the same compose environment block.

Intel/AMD GPU passthrough via /dev/dri is not reliably supported in WSL2 and is not recommended.

Fallback Behavior

If GPU initialization fails, the relay automatically falls back to SwiftShader (software WebGL). No intervention required — the server logs will show which mode was selected at startup.