Environment variables
Names and meanings only. Never commit values: copy apps/server/.env.example to apps/server/.env for local work, and use your host's secret store in production.
The server validates its configuration once at start-up and stops on any invalid value. Its start-up log prints names and whether secrets are set, never their values.
Secret means: treat as a password. Don't log it, don't paste it into tickets, rotate it if exposed.
The source of truth for this page is apps/server/src/config.ts (the schema and its defaults) plus the few variables read in apps/server/src/index.ts, apps/server/src/version.ts and apps/server/src/http/deployment.ts.
Core
| Name | Secret | Default | Meaning |
|---|---|---|---|
NODE_ENV | development | development, test or production. In production, APP_ORIGIN must be set explicitly, with https:, a bare origin and not localhost. | |
ROLE | all | Which part of the server runs: api, worker or all. The production start commands pass the role as --role=api / --role=worker, which overrides this variable. | |
PORT | 8787 | HTTP port. | |
HOST | 0.0.0.0 | Interface to listen on. | |
DATABASE_URL | Secret | (required) | PostgreSQL 16 connection string. Contains the database password. |
APP_ORIGIN | http://localhost:5173 | Public origin of the web app. Used for CORS (only this origin is allowed, with credentials), cookies, the WebSocket origin check and share links. Required in production, and there it must be an https: origin without a path and not localhost. | |
QUOTE_SECRET | Secret | (required) | 64 hex characters (32 bytes). HMAC key for quote tags. Same value on every api instance. Sessions don't depend on it. |
LOG_LEVEL | info | fatal, error, warn, info, debug or trace. |
Deployment
| Name | Secret | Default | Meaning |
|---|---|---|---|
TRUST_PROXY | empty (trust nothing) | Comma-separated IP addresses or CIDR ranges of the reverse proxies directly in front of the api, for example 10.0.0.0/8,fd00::/8. Only requests from these peers may set X-Forwarded-For, which is then used as the client address for per-IP limits. true, 1, hop counts and catch-all ranges such as 0.0.0.0/0 are refused at start-up, because they would let any client choose its own address. With it empty, behind a proxy, every request looks like it comes from the proxy, so per-IP limits (new guest sessions, WebSocket connections, Strike connection attempts, leaderboard requests) apply to the proxy as a whole. Set it to your platform's documented proxy range. With CLIENT_IP_HEADER set it is optional: it then applies only to requests without a usable header. | |
CLIENT_IP_HEADER | unset | The lower-case name of a header that your platform's edge sets to the client's IP address and that clients cannot forge, for example cf-connecting-ip on Render (the Render Blueprint sets it). When set, per-IP limits use the header's value whenever it holds exactly one valid IP address (no list, no port); otherwise the connection address applies, under the TRUST_PROXY rules (logged once per process). Unset, the header is never read. x-forwarded-for and forwarded are refused: those go through TRUST_PROXY. Set it only when every request reaches the api through that edge. | |
WEB_DIST | unset | Directory of the built web app (apps/web/dist). When set, the api role serves it, so the app and the API share one origin. The Docker image sets it to /app/web. | |
SBX_VERSION | the deployed commit | A version string reported by GET /api/health and in the WebSocket hello message. Unset, the server reports the deployed commit: the first 12 characters of RENDER_GIT_COMMIT, SOURCE_VERSION or GIT_COMMIT, whichever the platform sets (Render sets the first), or dev without any of them. | |
SBX_MIGRATIONS_DIR | next to the bundle | Overrides where the SQL migrations are read from. Normally not needed: the image ships them with the server. | |
SBX_FONTS_DIR | found automatically | Directory with the Manrope font files used to draw replay preview images. Normally not needed. |
Modes and switches
| Name | Secret | Default | Meaning |
|---|---|---|---|
PRACTICE_ENABLED | true | false rejects new practice taps with MODE_DISABLED. Accepted boxes still settle and refund. | |
LIVE_TRADING_ENABLED | false | Kill switch for Live mode. false refuses new Live connections and new entries. Closing open boxes, reading the account, disconnecting, reconciliation and deadline exits keep running. Live also needs STRIKE_BUILDER_CODE and KEY_ENCRYPTION_KEY, and on mainnet LIVE_MAINNET_ENABLED. | |
LIVE_MAINNET_ENABLED | false | Must also be true for Live trading when STRIKE_NETWORK=mainnet, because orders there use real funds. Without it, Live mode stays unavailable on a mainnet deployment. Reading mainnet's public market data (STRIKE_DATA_NETWORK=mainnet) needs no switch: it never moves funds. |
Boolean variables accept true, false, 1 or 0.
Strike networks
Each Strike network has one host: api-v2-testnet.strikefinance.org for testnet and api.strikefinance.org for mainnet. A deployment names two networks, which may be the same: the execution network (STRIKE_NETWORK), where Live orders are placed and whose market Live mode shows, and the data network (STRIKE_DATA_NETWORK), whose public market data drives practice mode. See Strike integration.
The Strike URLs default to their network's host. An override must use that same host; any other host, including the other network's, stops the server. There is no fallback between networks. <exec host> and <data host> below are the two networks' hosts.
| Name | Secret | Default | Meaning |
|---|---|---|---|
STRIKE_NETWORK | testnet | testnet or mainnet: the execution network. Builder Connect, orders, account reads and the user stream go there, and Live mode's board, liquidity gate and expected fill use its market. The hosted app uses testnet. Live trading on mainnet also needs LIVE_MAINNET_ENABLED=true. | |
STRIKE_DATA_NETWORK | the execution network | testnet or mainnet: the data network. The practice board, practice quotes and practice settlement use its mark prices, and the board shows its order book and statistics. The hosted app uses mainnet (public market data only). | |
STRIKE_REST_ORIGIN | https://<exec host> | The execution network's REST origin (trading, account, Builder Connect, market rules, and Live mode's market data when the networks differ). No path and no custom port. | |
STRIKE_USER_WS | wss://<exec host>/ws/user-api | The execution network's user stream. | |
STRIKE_PRICE_WS | wss://<data host>/ws/price | The data network's public price stream. | |
STRIKE_DATA_REST_ORIGIN | STRIKE_REST_ORIGIN when the networks are the same, else https://<data host> | The data network's REST origin for public market data (order book snapshots, best bid and ask, trades, statistics). No path and no custom port. | |
STRIKE_EXEC_PRICE_WS | STRIKE_PRICE_WS when the networks are the same, else wss://<exec host>/ws/price | The execution network's public price stream: Live mode's own market when the networks differ. Must be on the execution network's host. | |
STRIKE_ALLOW_MOCK_HOSTS | unset | Tests only: allows loopback hosts for the mock Strike. Refused unless NODE_ENV=test. |
The overrides are rarely needed: setting the two networks is enough. When the networks are the same, one override of STRIKE_REST_ORIGIN or STRIKE_PRICE_WS covers both uses, for example to point everything at the mock in tests.
Live mode
| Name | Secret | Default | Meaning |
|---|---|---|---|
STRIKE_BUILDER_CODE | unset | The Strike builder code used for Builder Connect: strikeabox, on both networks. Live mode is unavailable without it. | |
STRIKE_BUILDER_FEE_CAP_BPS | 100 | The maximum builder fee users are asked to approve when they connect (fee_share_bps), in basis points, 0–100. 100 (1 %) is the default and what the hosted deployment uses. | |
STRIKE_BUILDER_FEE_BPS | 5 | The builder fee actually charged per order (X-Builder-Fee-Bps), in basis points, 0–100. Must not exceed the cap, or the server refuses to start. 5 (0.05 %) is the default and what the hosted deployment uses. The rate is stored on each live box when it is accepted and used for all of its orders; an order is never charged more than the user approved. | |
STRIKE_REFERRAL_CODE | unset | Optional Strike referral code to request when users connect. If set, the message users sign shows the referral; if unset it says "Referral attribution: none". Unset on the hosted deployment. | |
KEY_ENCRYPTION_KEY | Secret | unset | 64 hex characters (32 bytes). AES-256-GCM key for stored Strike API-wallet private keys. Same value on api and worker. Live mode is unavailable without it, and the worker does not start its Live part. |
KEY_ENCRYPTION_KEY_ID | k1 | Label stored with each encrypted key. It detects a changed KEY_ENCRYPTION_KEY; it does not enable rotation (only one key is supported). |
Development and tests only
These are not part of a deployment.
| Name | Used by | Meaning |
|---|---|---|
RETENTION_INITIAL_DELAY_MS | worker | Shortens the first market-data retention run after start-up (default 60 s). For tests. |
SBX_TEST_DATABASE_URL | practice and integration tests | Database for the server's practice, HTTP and process tests. Default postgres://sbx:sbx@127.0.0.1:5432/sbx_test_practice. |
SBX_TEST_SOCIAL_DATABASE_URL | social tests | Database for the social tests. Default postgres://sbx:sbx@127.0.0.1:5432/sbx_test_social. |
MARKET_TEST_DATABASE_URL | market tests | Database for the market tests. Default postgres://sbx:sbx@127.0.0.1:5432/sbx_test_market. The tests drop and recreate its schema, so use a throwaway database. |
SBX_TEST_PG | Strike tests | Postgres server for the Strike tests. Default postgres://sbx:sbx@127.0.0.1:5432, using the database sbx_test_strike (created if missing); a database name in the URL path replaces it. |
VITE_API_TARGET | web dev server | Where Vite proxies /api, /og and /ws to. Default http://localhost:8787. |
Generating secrets
openssl rand -hex 32 # QUOTE_SECRET, KEY_ENCRYPTION_KEYUse different values per environment. Rotating QUOTE_SECRET invalidates quotes in flight (they last 3 seconds); sessions are unaffected. There is no rotation procedure for KEY_ENCRYPTION_KEY: changing it makes every stored Strike key unreadable. Users must then reconnect Live mode; a live box whose close needs the old key is reported CLOSE_FAILED until they do (or until the previous key is restored), and its close resumes after that.