Skip to content

Environment Variables

Every variable, which service reads it, and its default. Sourced from each service's config module and .env.example, cross-checked against infra/k8s/*-deployment.yaml.

Core

VariableServicesDefaultNotes
NODE_ENValldevelopmentproduction in the cluster
ENVapidevelopmentSeparate from NODE_ENV; only the API reads it
PORTapi, esu, anansi, igdb-heartbeat, webvariesapi 3000→set to 4000, esu 4010, anansi 4005, igdb 4006, web 3000
DATABASE_URLapi, esu, ogun, anansi, igdb-heartbeatRequired. postgresql://user:pass@host:5432/db

Redis

VariableServicesDefaultNotes
REDIS_URLapi, esu, ogunTakes precedence over host/port where both are read
REDIS_HOSTapi, esu, ogun, anansi, queuelocalhost
REDIS_PORTapi, esu, ogun, anansi, queue6379
REDIS_PASSWORDapi, esu, ogun, anansi, queueplaypalz locally — the Compose Redis requires it

Authentication

VariableServicesDefaultNotes
JWT_SECRETapi, esu"supersecretkey" / ""Must be identical and must be set in production
RESET_SECRETapi"supersecretkey"Password reset tokens
JWT_EXPIRATION_MINUTESapi60Read into config but unusedgenerateToken hard-codes 7 days
JWT_ALGesuHS256

The JWT defaults are publicly known

apps/api/src/configs/index.ts falls back to "supersecretkey". Anyone who knows that string can mint a valid token for any user. Production must set it from the api-secrets sealed secret.

Interservice

VariableServicesDefaultNotes
REALTIME_SERVICE_URLapi, ogunMeans two different things — see below
REALTIME_ADMIN_TOKENapi, esu, ogun""Shared secret for /admin
API_BASE_URLesuhttp://localhost:3000/api/v1⚠️ Set to :4000 locally
API_SERVICE_TOKENesu""Sent as X-Service-Token
WEB_APP_URLapihttp://localhost:3000CORS origin

REALTIME_SERVICE_URL is inconsistent

The API's helper appends /admin; ogun's does not:

ts
fetch(`${REALTIME_SERVICE_URL}/admin${path}`)   // apps/api/src/lib/interservice.ts
fetch(`${REALTIME_SERVICE_URL}${path}`)          // apps/ogun/src/utils/interservice.ts

Both deployments set http://playpalz-esu:4010, which is correct for the API and wrong for ogun. Both .env.example files ship http://localhost:4010/admin, which is correct for ogun and wrong for the API. See Service Topology.

Object storage

The variable names differ between services — an artifact of them being written at different times.

VariableServicesNotes
SPACES_REGIONogun, igdb-heartbeate.g. nyc3
SPACES_ENDPOINTogunRead by ogun
SPACES_URLogun, igdb-heartbeatRead as the endpoint by igdb-heartbeat
SPACES_ACCESS_KEYogun, igdb-heartbeat
SPACES_SECRET_KEYogun, igdb-heartbeat
SPACES_BUCKETapi, ogun, igdb-heartbeat
PUBLIC_CDN_URLigdb-heartbeatCDN base for cover images
AWS_ACCESS_KEY_IDapiAlternative naming in .env.example
AWS_SECRET_ACCESS_KEYapi
AWS_REGIONapi
AWS_S3_BUCKETapi

The spaces-credentials Kubernetes secret (created by Terraform) supplies AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, S3_ENDPOINT, S3_URL, S3_BUCKET, and CDN_ENDPOINT. Deployments map those into whichever names their service expects. Set both families locally to avoid surprises.

Mux

VariableServicesNotes
MUX_ACCESS_TOKENapi, ogun
MUX_SECRET_KEYapi, ogun
MUX_WEBHOOK_SECRETapiConfigured but never verified — see Webhooks

Two secret shapes for Mux

ogun-deployment.yaml still reads a mux-credentials secret with TOKEN_ID / TOKEN_SECRET, while the sealed mux-secrets uses MUX_ACCESS_TOKEN / MUX_SECRET_KEY. Reconcile onto one.

LiveKit

VariableServicesNotes
LIVEKIT_HOSTapiHTTP API base
LIVEKIT_WS_URLapiClient WebSocket URL
LIVEKIT_API_KEYapidevkey locally
LIVEKIT_API_SECRETapisecret locally

Payments

VariableServicesDefaultNotes
REVENUECAT_WEBHOOK_SECRETapiIf unset, the webhook accepts anything
STRIPE_SECRET_KEYapi, anansi""Platform account
STRIPE_PUBLISHABLE_KEYapi
PLATFORM_FEE_PCTanansi20Integer percent
MIN_PAYOUT_CENTSanansi100$1.00
COST_PER_COINanansi0.02USD per Play Coin
PAYOUT_LOCK_TTL_MSanansi3600000Redis lock TTL
ADMIN_SECRETanansi""Empty default — set it

IGDB

VariableServicesNotes
IGDB_CLIENT_IDigdb-heartbeatTwitch application
IGDB_CLIENT_SECRETigdb-heartbeat

esu

VariableDefaultNotes
CORS_ORIGIN*Comma-separated
SOCKET_EVENT_LOGGINGtrueLogs every socket event
ADMIN_UI_ENABLEDfalseSocket.IO Admin UI
ADMIN_UI_USERNAME""Required when enabled
ADMIN_UI_PASSWORD_HASH""bcrypt hash; generate with pnpm --filter @playpals/esu admin-ui:creds

web (build-time)

VariableProductionNotes
NEXT_PUBLIC_API_URLhttps://api.playpalz.gg/api/v1Must include /api/v1
NEXT_PUBLIC_API_TIMEOUT10000ms
HOSTNAME0.0.0.0Required in a container
NEXT_TELEMETRY_DISABLED1

NEXT_PUBLIC_* values are inlined at build time. Changing one requires a rebuild — setting it in the deployment manifest alone does nothing for client code.

mobile (build-time)

VariableNotes
EXPO_PUBLIC_ENVdevelopment | staging | production — drives app name and scheme
EXPO_PUBLIC_API_URLMust include /api/v1
EXPO_PUBLIC_REALTIME_URLesu base URL, no path
BUGSNAG_API_KEYSet per EAS profile

Baked in at build time by the EAS profile. See Builds & Releases.

Minimum local set

To get the stack running with everything else stubbed:

bash
# packages/database/.env
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"

# apps/api/.env
PORT=4000
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
REDIS_URL="redis://:playpalz@localhost:6379"
JWT_SECRET="local-dev-secret"
RESET_SECRET="local-dev-reset"
REALTIME_SERVICE_URL="http://localhost:4010"
REALTIME_ADMIN_TOKEN="changeme"
WEB_APP_URL="http://localhost:3000"

# apps/esu/.env
PORT=4010
JWT_SECRET="local-dev-secret"          # must match the API
API_BASE_URL="http://localhost:4000/api/v1"
REALTIME_ADMIN_TOKEN="changeme"
REDIS_URL="redis://:playpalz@localhost:6379"

# apps/mobile/.env
EXPO_PUBLIC_ENV=development
EXPO_PUBLIC_API_URL=http://localhost:4000/api/v1
EXPO_PUBLIC_REALTIME_URL=http://localhost:4010

Note the two deviations from the shipped .env.example files: REALTIME_SERVICE_URL without /admin, and API_BASE_URL on port 4000.

Adding a variable

Four places, every time:

  1. The service's config module (src/configs/index.ts or src/config/index.ts)
  2. That service's .env.example
  3. infra/k8s/<service>-deployment.yaml — plus a sealed secret if it is sensitive
  4. This page

Internal documentation — PlayPalz platform