← Back to Playbook

Infrastructure & Container Architecture

How Komodo manages the server, how stacks map to environments, and how game containers plug into the platform.

What runs on 91.189.177.18

One Ubuntu server. System Nginx handles SSL and routes traffic. Komodo manages all Docker stacks. Each stack is an independent group of containers with its own lifecycle.

Ubuntu Server — 91.189.177.18
🔀
System Nginx
SSL termination · routes :80/:443 → containers
🎛
Komodo
Stack manager — points at this repo's stacks/
↓ manages ↓
earena-test test
frontend:3011
backend:8011
postgres:5433
redis:6380
workercelery
earena-prod prod
frontend:3010
backend:8010
postgres:5432
redis:6379
workercelery
cs2-api game
cs2-api:4001
pubg-api game
pubg-api:4002
cs-web-test test
cs-web:3021
cs-web-prod prod
cs-web:3020
pubg-web future
pubg-web:3030
cs2-server dedicated
cs2-dedicated:27015
openobserve obs
openobserve:5080
OTLP HTTP:5081
komodo system
komodo-core:9120
periphery:8120
mongo:27017
playbook docs
nginx:9200
new-game-api future
game-api:400x

Test and prod as separate stacks

Test and prod are identical stacks running on different ports. Komodo manages each independently — deploy, rollback, and restart one without touching the other. Both point at this git repo; prod tracks main, test tracks main or a specific branch.

🟡 earena-test
frontend (Next.js)127.0.0.1:3011
backend (FastAPI)127.0.0.1:8011
postgres5433
redis6380
worker (Celery)internal
↑ Nginx proxies staging.esport-arena.no here
Komodo: can track a branch or PR · redeploy manually or on push to branch
🟢 earena-prod
frontend (Next.js)127.0.0.1:3010
backend (FastAPI)127.0.0.1:8010
postgres5432
redis6379
worker (Celery)internal
↑ Nginx proxies 2.esport-arena.no here
Komodo: tracks main branch · auto-redeploy on image push · env in Komodo secrets

How containers share auth and data

All stacks run on the same server and Docker network. Auth is stateless (JWT). The main Postgres is the single source of truth — game containers use dedicated schemas, never their own separate databases.

🔑 Clerk Auth — shared session across all containers
@media (max-width:680px) { grid-template-columns: 1fr; }
Frontend containers (sanity-web, cs-web)
Clerk sets a cookie on esport-arena.no (root domain) — automatically sent to every path including /cs2/*
Both apps use the same CLERK_PUBLISHABLE_KEY — user sees one seamless login
Server Components call auth() — Clerk middleware reads the cookie, no extra config needed
Backend API containers (cs2-api, pubg-api)
Frontend Server Components fetch the API with Authorization: Bearer <clerk-token>
API containers verify the JWT using Clerk's JWKS endpoint — stateless, no DB lookup needed for auth
Each backend sets CLERK_JWKS_URL — Komodo injects via env, never in repo
🗄 Postgres — one instance, separate schemas
earena-prod · postgres:5432
schema: public
Users, teams, tournaments, orgs
schema: cs2
Matches, rounds, player stats, maps
schema: pubg
Telemetry, squad stats, drop zones
Each game API container connects with its own Postgres role scoped to its schema: ?search_path=cs2
Game APIs call earena-backend's internal API for shared data (user profiles, team rosters) — never read the public schema directly
Docker network: containers reach each other by service name — postgres:5432, earena-backend:8010
Connection string stored as Komodo secret — never in repo. Each schema has its own migration tool (Alembic per service)
📡 OpenObserve — all containers ship logs here
Every container sets OTEL_EXPORTER_OTLP_ENDPOINT=http://openobserve:5081 — logs and traces ship automatically
Log format: JSON, always includes service, level, trace_id — searchable in OpenObserve UI
No console.log in production — ESLint rule blocks it. Use the structured logger wrapper instead
Required env vars (all stacks)
OTEL_SERVICE_NAME=cs2-api
OTEL_EXPORTER_OTLP_ENDPOINT=
  http://openobserve:5081
OTEL_EXPORTER_OTLP_HEADERS=
  Authorization=Basic <token>

How sanity-web connects to game containers

The frontend has dynamic game routes at /(games)/[slug]/... and game-specific routes like /pubg/tournament/[slug]. Each game slug maps to a dedicated API container via an environment variable. Adding a new game means adding a new container stack — the frontend needs no structural changes.

URL Routes (sanity-web)
/[locale]/cs2/tournament/[slug]
/[locale]/cs2/tournament/[slug]/dropmap
/[locale]/cs2/[slug]
/[locale]/pubg/tournament/[slug]
/[locale]/pubg/player/[ign]
/[locale]/pubg/[slug]
/[locale]/[future-game]/...
🌐
sanity-web
Next.js frontend
Server Component fetches
reads env vars to find game APIs
Game API Containers
🔴 cs2-api stack: cs2-api
env var: CS2_API_URL=http://cs2-api:4001
GET /tournament/:slug
GET /tournament/:slug/dropmap
GET /match/:id/stats
GET /player/:steam-id
🟢 pubg-api stack: pubg-api
env var: PUBG_API_URL=http://pubg-api:4002
GET /tournament/:slug
GET /player/:ign/stats
GET /match/:id/telemetry
⬜ new-game-api stack: new-game-api
env var: NEW_GAME_API_URL=http://...
GET /tournament/:slug
GET /player/:id

How Komodo deploys a stack

Komodo reads compose.yaml directly from this Git repo. You never SSH to deploy manually — Komodo handles pull, build, and restart. Secrets live in Komodo, never in the repo.

📝
Edit compose.yaml
in stacks/<name>/ and commit to main
🚀
CI builds image
GitHub Actions pushes to ghcr.io/esportarena/…
🎛
Komodo redeploys
pulls latest image, recreates containers
Live
Nginx routes traffic to new containers

Adding a new game container

The pattern is the same for every game. Four steps from idea to live on the platform.

1

Create the stack

Add stacks/<game>-api/compose.yaml in core-infrastructure. Define the container image, port, and env file.

2

Register in Komodo

In Komodo UI, create a new Stack pointing at stacks/<game>-api/ in this repo. Add secrets to Komodo — never in the repo.

3

Add env var to sanity-web

In Komodo, add <GAME>_API_URL=http://<game>-api:400x to the earena-prod and earena-test stack environments.

4

Add frontend routes

In sanity-web, add routes under app/[locale]/(games)/<game>/. Fetch from the game API using the env var. Create a feature doc first.

Multi-Zones: game frontends as separate containers

Heavy game-specific UI — 3D map renderers, live telemetry dashboards, matchmaking flows — does not belong in the main sanity-web bundle. Each game gets its own Next.js container with its own basePath, served under the same domain via Nginx. Users see one seamless URL; each app ships and scales independently.

Nginx routes traffic by path prefix
esport-arena.no/*
sanity-web :3010
main platform, CMS content, auth
esport-arena.no/cs2/*
cs-web :3020
CS2 maps, telemetry, matchmaking UI
esport-arena.no/pubg/*
pubg-web :3030 future
PUBG dropmap, squad view, standings
Why separate containers, not just separate routes?
ConcernSingle sanity-web bundleMulti-Zones (separate container)
Bundle size 3D map renderer + telemetry shipped to every visitor Only loaded when user navigates to /cs2/*
Deploy risk CS2 bug can break the main platform Fully isolated — redeploy cs-web without touching sanity-web
Scaling Scale the entire app for one game's traffic spike Scale cs-web independently during CS2 tournament day
Team ownership All game features compete in one PR queue CS2 team owns cs-web repo end to end
Cache strategy Shared CDN cache invalidation Game-specific cache headers per container
next.config.ts — cs-web
// cs-web knows it lives under /cs2
const nextConfig = {
  basePath:    '/cs2',
  assetPrefix: '/cs2',
};

// Nginx routes /cs2/* → cs-web container
// Clerk cookie domain shared: esport-arena.no
// Session works seamlessly across both apps

Stack directory

StackPath in repoEnvPurpose
earena-teststacks/earena/testStaging environment — same stack, different ports
earena-prodstacks/earena/prodMain platform — frontend, backend, DB, worker
cs-web-teststacks/cs-web/testCS2 frontend Multi-Zone — basePath /cs2, port 3021
cs-web-prodstacks/cs-web/prodCS2 frontend Multi-Zone — basePath /cs2, port 3020
cs2-apistacks/cs2-api/gameCS2 game data API — tournaments, maps, stats
pubg-apistacks/pubg-api/gamePUBG game data API — tournaments, telemetry, players
cs2-serverstacks/cs2/dedicatedCS2 dedicated game server — port 27015
openobservestacks/openobserve/obsCentralized logs + traces — OTLP on :5081, UI on :5080
komodostacks/komodo/systemKomodo Core + Periphery + MongoDB
playbookstacks/playbook/docsDev playbook static site — port 9200