Skip to content

Production Container Architecture

This page documents the production runtime shape of Swagg Bet by container, not by source-code layer. The diagram follows the same left-to-right flow as the logic architecture reference: users, edge proxy, app surfaces, core API, providers, and database.

Use this page when deploying, debugging traffic, checking service ownership, or explaining which process is responsible for a failure.

Production container architecture

Vector source: container-architecture.svg

What This Diagram Shows

The production stack has one host-level reverse proxy, application containers (Next.js surfaces, API, and workers), one database container, and several external provider systems.

  • Nginx is the public edge. It terminates TLS and proxies domains to local container ports.
  • Next.js containers serve user-facing surfaces. web, admin, support, and p2p are separate deployable images.
  • api is the core backend runtime. It handles Hono routes, provider callbacks, money flows, and in-process background jobs.
  • postgres is the only datastore container. The main swaggbet database and the separate p2p database live in the same Postgres service.
  • Notifications, analytics, Customer.io, and realtime are worker containers under apps/workers with separate images. Analytics and Customer.io run only when their complete credential pairs are configured. Transactional email is sent in-process by api via EMAIL_PROVIDER (Resend, MailerSend, or UniOne).
  • redis backs WebSocket tickets and pub/sub fan-out for the realtime worker. The API also connects to Redis for ticket minting.
  • Workspace packages are build-time code, not runtime services. Packages such as @repo/db, @repo/auth, @repo/ui, and @repo/wallet are bundled into the app images that import them.

Production Traffic Paths

FlowRuntime pathNotes
Player siteBrowser -> Nginx -> web:3000 -> api:8787 -> postgres:5432Normal casino lobby, wallet, profile, game, and VIP traffic.
Admin panelBrowser -> Nginx -> admin:3001 -> api:8787 and postgres:5432Only admin uses direct SQL among the Next.js frontends. web and support call the API only.
Support deskBrowser -> Nginx -> support:3002 -> api:8787 -> postgres:5432Support staff workspace and ticket operations.
P2P paymentBrowser -> Nginx -> p2p:3005 -> postgres:5432/p2p -> api:8787 webhookP2P has its own database schema and calls back into the casino API.
Transactional emailapi:8787 → Resend, MailerSend, or UniOneIn-process via apps/api/src/services/email (EMAIL_PROVIDER).
In-app notificationsnotifier:8790postgresCampaign outbox delivery worker.
Customer.io CRM eventscustomerio:8791postgres → Customer.io Classic Track APIOptional; selected by complete Customer.io credentials. Run one delivery replica.
Analytics streamingstreamer:8789postgres → ClickHouseOptional; selected by complete ClickHouse credentials.
Realtime messagingrealtime:8792postgres + redisClaims private_state_outbox and realtime_event_outbox, publishes to Redis for WebSocket fan-out.
Provider callbacksExternal provider -> Nginx -> api:8787 -> postgres:5432Game platform, payment, and webhook traffic terminates at the API.

Container Inventory

Source of truth: docker/compose/docker-compose.yml.

Compose serviceContainerRuntimePortPublic domainHealth check
webswaggbet-webNext.js3000swagg.bet/api/health
adminswaggbet-adminNext.js3001admin.swagg.bet/api/health
supportswaggbet-supportNext.js3002support.swagg.bet/api/health
p2pswaggbet-p2pNext.js3005pay.swagg.bet/api/internal/ensure-admin
apiswaggbet-apiHono8787api.swagg.bet/health
streamerswaggbet-streamerWorkers analytics8789Internal only/health
notifierswaggbet-notifierWorkers notifications8790Internal only/health
customerioswaggbet-customerioWorkers Customer.io8791Internal only/health
realtimeswaggbet-realtimeWorkers realtime8792Internal only/health
redisswaggbet-redisRedis 76379Internal onlyredis-cli ping
postgresswaggbet-postgresPostgreSQL 165432Internal onlypg_isready

Container Responsibilities

web

Player-facing Next.js app for the casino lobby, games, wallet, VIP, settings, payment-result screens, and public player flows.

Owns: UI rendering and browser-side player experience.

Does not own: database queries or provider orchestration. It calls api through NEXT_PUBLIC_API_URL.

admin

Back-office Next.js app for operators. It manages players, withdrawals, bonuses, games, support, marketing, roles, permissions, audit logs, and campaign tools.

Owns: admin UI, permission-guarded Next.js route handlers, and audited admin mutations.

Important production detail: admin is the only Next.js frontend with direct database access. Some admin route handlers call @repo/db operations directly with DATABASE_URL. web and support do not connect to Postgres in production. Keep admin handlers thin and route database access through operations.

support

Support staff Next.js app for ticket and team workflows.

Owns: support-agent UI and staff workspace flows.

Depends on: api for support conversations, ticket state, auth, and related data.

p2p

Standalone payment-provider app. It has its own runtime surface and its own p2p database inside the same Postgres container.

Owns: P2P payment pages, provider admin credentials, receipt uploads, and payment lifecycle state.

Calls back to: api:8787/webhook/p2p when a payment changes state.

api

Core Hono backend. This is the main runtime for casino business behavior.

Owns:

  • player, wallet, game, VIP, support, platform, payment, notification, and webhook routes;
  • provider calls for games, payments, wallet networks, and internal P2P;
  • background work such as session cleanup, bonus scheduling, game sync, exchange rates, and support maintenance.

Important production detail: there is no Celery or separate queue worker container beyond apps/workers entrypoints. Background jobs such as session cleanup run inside api. Realtime delivery is handled by the realtime worker plus Redis, not inside api.

notifier

In-app notification campaign delivery worker (apps/workers notifications entrypoint).

Owns: scheduled campaign promotion, audience enqueue, and delivery outbox processing.

customerio

Customer.io Classic Track API event delivery worker (apps/workers customerio entrypoint).

Owns: customerio_outbox claim/delivery. Prefer a single delivery replica until multi-worker claim behavior is load-tested.

streamer

Analytics worker that moves queued event data from Postgres to external ClickHouse (apps/workers analytics entrypoint).

Owns: polling, worker identity, event locking, batching, and ClickHouse inserts.

Does not own: user-facing analytics UI or product behavior. /health is liveness-only; backlog depth is exposed on /ready and metrics.

realtime

Private realtime outbox worker (apps/workers realtime entrypoint).

Owns: polling private_state_outbox (wallet balance) and realtime_event_outbox (support, withdrawal, notifications), Redis pub/sub publish, stale-lock recovery.

Depends on: postgres, redis. API WebSocket gateway subscribes to the same Redis channels.

Does not own: ticket minting or WebSocket termination (those live in api).

postgres

Primary database container.

Databases:

  • swaggbet for the main casino schema and Drizzle migrations.
  • p2p, created by docker/postgres/init/02-create-p2p-db.sql, for the standalone payment provider.

Volumes:

  • postgres_data for database durability.
  • p2p_uploads belongs to p2p, not Postgres, and stores uploaded payment receipt files.

Public Edge Mapping

Nginx config lives in nginx/*.conf and maps public domains to local ports.

DomainUpstream
swagg.betlocalhost:3000
admin.swagg.betlocalhost:3001
support.swagg.betlocalhost:3002
pay.swagg.betlocalhost:3005
api.swagg.betlocalhost:8787
docs.swagg.betstatic VitePress files on disk

Operational Checks

When production is unhealthy, check from the edge inward:

  1. Public domain and TLS: confirm the Nginx server block and certificate for the domain.
  2. Container health: check the container health endpoint from the inventory table.
  3. Internal dependency: check whether the container can reach api, postgres, p2p, or the external provider it depends on.
  4. Environment: verify DATABASE_URL, REDIS_URL, NEXT_PUBLIC_API_URL, provider keys, webhook URLs, and service secrets for the affected flow.
  5. Database: confirm whether the failing flow uses the main swaggbet database or the separate p2p database.

Deployment Notes

Production images are built from the repo root:

bash
./build.sh
docker compose up -d

Image build pattern:

RuntimeDockerfileBuild args
Next.js appsdocker/Dockerfile.nextjsAPP_NAME, APP_PORT, app-specific public URLs
Hono apps/workersdocker/Dockerfile.honoAPP_NAME
Shared basedocker/Dockerfile.baseworkspace dependencies and build foundation

Mermaid Source

This Mermaid version is intentionally simpler than the SVG. It is useful for quick edits, not for final presentation.

mermaid
flowchart LR
  user["Players, admins, support"] --> nginx["Nginx TLS reverse proxy"]

  nginx --> web["web :3000"]
  nginx --> admin["admin :3001"]
  nginx --> support["support :3002"]
  nginx --> p2p["p2p :3005"]
  nginx --> api["api :8787"]

  web --> api
  admin --> api
  support --> api
  admin -. "direct SQL (admin only)" .-> postgres["postgres :5432"]

  api --> postgres
  api --> p2p
  p2p --> postgres
  p2p -. "webhook" .-> api

  notifier["notifier :8790"] --> postgres
  customerio["customerio :8791"] --> postgres
  streamer["streamer :8789"] --> postgres
  realtime["realtime :8792"] --> postgres
  realtime --> redis["redis :6379"]
  api --> redis

  api --> providers["game, payment, wallet providers"]
  api --> email["Resend, MailerSend, or UniOne"]
  customerio --> cio["Customer.io Track API"]
  streamer --> clickhouse["ClickHouse"]

Rule of Thumb

If the code is shared behavior, put it in packages/*. If it needs its own deployment boundary, health check, persistent volume, or blast-radius isolation, make it a container. Most new casino product logic should go into api or a shared package, not a new service.