Skip to content

Production Container Architecture ​

This page documents the production runtime shape of Omadx 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.

Public brand and hostnames are Omadx / omadx.com. Docker Compose still uses project name swaggbet, so container names, the main Postgres database, Grafana service_name labels, and on-host backup paths remain swaggbet-*.

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 (Compose name; the product is Omadx) 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:8790 → postgresCampaign outbox delivery worker.
Customer.io CRM eventscustomerio:8791 → postgres → Customer.io Classic Track APIOptional; selected by complete Customer.io credentials. Run one delivery replica.
Analytics streamingstreamer:8789 → postgres → ClickHouseOptional; selected by complete ClickHouse credentials.
Realtime messagingrealtime:8792 → postgres + 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.js3000omadx.com/api/health
adminswaggbet-adminNext.js3001admin.omadx.com/api/health
supportswaggbet-supportNext.js3002support.omadx.com/api/health
p2pswaggbet-p2pNext.js3005pay.omadx.com/api/internal/ensure-admin
apiswaggbet-apiHono8787api.omadx.com/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; analytics replica may publish 5432 to POSTGRES_REPLICATION_CLIENT_CIDRS 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 (Compose database name).
  • 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
omadx.comlocalhost:3000
admin.omadx.comlocalhost:3001
support.omadx.comlocalhost:3002
pay.omadx.comlocalhost:3005
api.omadx.comlocalhost:8787
promo.omadx.comcampaign landing (apps/promo)
docs.omadx.comstatic 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 (Omadx casino schema) 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.