Environments
There are effectively two environments today: your laptop, and production.
| Environment | Where | Status |
|---|---|---|
| Local | Docker Compose + pnpm dev | Working |
| Staging | Planned in playpalz-staging namespace | Not deployed |
| Production | DOKS default namespace | Live |
There is no staging environment
apps/mobile/eas.json builds a preview profile pointing at staging-api.playpalz.gg and staging-socket.playpalz.gg. Those hosts are not in infra/terraform/dns.tf, and the staging Ingress in infra/k8s/ingress.yaml is commented out.
So a staging build has nowhere to connect, and every change goes from a laptop straight to production. Standing up staging is the highest-value infrastructure work available, and there is already a plan for it.
Local
| Service | Port | How |
|---|---|---|
| PostgreSQL | 5432 | docker compose up -d db |
| Redis | 6379 | docker compose up -d redis (password playpalz) |
| LiveKit | 7880–7882 | docker compose up -d livekit (devkey / secret) |
| api | 4000 | pnpm --filter @playpals/api dev |
| esu | 4010 | pnpm --filter @playpals/esu dev |
| ogun | — | pnpm --filter @playpals/ogun dev |
| anansi | 4005 | pnpm --filter @playpals/anansi dev |
| web | 3000 | pnpm --filter @playpals/web dev |
| docs | 5173 | pnpm --filter @playpals/docs dev |
pnpm dev starts api, esu, ogun, and mobile together; pnpm dev:all starts everything.
Third-party services degrade gracefully — without Spaces, Mux, RevenueCat, or Stripe credentials the app still runs; those features just do not work. Full setup: Local Setup.
Docker-based local
docker-compose.dev.yaml overlays live-reload containers for api, esu, and ogun on top of the base Compose stack:
./start-dev.sh # foreground, everything
./start-dev.sh -d api # detached, one serviceSlower than running natively, but eliminates environment drift between developers.
Production
Namespace default in the DOKS cluster.
| Service | Replicas | Strategy | Ingress |
|---|---|---|---|
web | 3 | RollingUpdate | www.playpalz.gg, playpalz.gg |
api | 3 | RollingUpdate | api.playpalz.gg |
esu | 2 | RollingUpdate | socket.playpalz.gg |
ogun | 2 (HPA 2–8) | RollingUpdate | — |
anansi | 1 | Recreate | — |
igdb-heartbeat | 1 | Recreate | — |
redis | 1 (StatefulSet + 5Gi PVC) | — | — |
The two singletons use Recreate rather than RollingUpdate deliberately: both are cron-driven, and briefly running two pods during a rollout risks a duplicate payout run or a duplicate sync.
anansi's Redis lock makes that safe in principle, but Recreate means never having to rely on it.
The staging plan
dev-notes/helm-chart-staging-production-plan.md proposes converting the raw manifests into one Helm chart deployed twice into the same cluster:
infra/helm/playpalz/
├── Chart.yaml
├── values.yaml shared defaults, production-shaped
├── values-staging.yaml
├── values-production.yaml
├── templates/ one directory per service
└── sealed-secrets/
├── staging/ re-sealed for the staging namespace
└── production/| Production | Staging | |
|---|---|---|
| Namespace | default | playpalz-staging |
| Hosts | *.playpalz.gg | staging.playpalz.gg, api-staging.playpalz.gg |
| Replicas | As above | 1 each |
| Resources | Full | Reduced |
Sealed secrets stay outside templates/ and are applied with kubectl apply — they are encrypted blobs scoped to a specific namespace and secret name, so Helm should not manage their lifecycle, and each one needs a one-time kubeseal re-seal for the staging namespace.
Prerequisites for staging
- DNS records for
staging.playpalz.ggandapi-staging.playpalz.ggindns.tf. - A separate PostgreSQL database (a second
digitalocean_database_dbon the same cluster is the cheap option). - A separate Redis, or a separate database index on the existing one.
- Sealed secrets re-sealed for
playpalz-staging. - Uncomment and template the staging
Ingress. - Point the mobile
previewprofile at the new hosts.
Reduced replicas and a shared database cluster keep the added cost to roughly $30–50/month.
Mobile environments
Independent of the backend deployment, driven by EXPO_PUBLIC_ENV:
| Profile | Env | API | Realtime |
|---|---|---|---|
development | development | localhost:4000 | localhost:4010 |
preview | staging | staging-api.playpalz.gg ⚠️ | staging-socket.playpalz.gg ⚠️ |
production | production | api.playpalz.gg | socket.playpalz.gg |
⚠️ Those hosts do not resolve. See Builds & Releases.
Known inconsistency between manifests
ogun-deployment.yaml still reads a mux-credentials secret with TOKEN_ID / TOKEN_SECRET keys, while the sealed secret in infra/k8s/sealed-secrets/mux-secrets.yaml uses MUX_ACCESS_TOKEN / MUX_SECRET_KEY — the names api-deployment.yaml consumes. Reconcile these onto one secret when porting to Helm.
