Skip to content

Service map

The complete local stack as wired by /home/ubuntu/ebit/docker-compose.yml. Five NestJS apps + two Next.js FEs + Postgres + two Redis + RabbitMQ + the four-process observability stack (OTel Collector / Jaeger / Prometheus / Loki / Grafana).

Generated 2026-04-25 from docker-compose.yml, CLAUDE.md, the four referenced memories, and cross-checked against the existing flow docs in ../flows/.

Top-level edges

Split into two pictures because client (player) and admin/ops traffic hit different surfaces of ebit-api and live with different downstream concerns. Both diagrams share the same infra column on the right; they're separate so each can be scanned without crossing the other's arrows.

A. Player path — ebit-feebit-api + ebit-rt + downstream

flowchart LR
    subgraph cli["Player"]
        user((Browser<br/>player))
    end

    subgraph fe["Frontend"]
        ebit_fe["ebit-fe :3000<br/>Next.js dropbet"]
    end

    subgraph apps["Player-serving Nest apps"]
        direction TB
        ebit_api["ebit-api :4000<br/>NestJS REST + Swagger"]
        ebit_rt["ebit-rt :4001<br/>socket.io /events"]
        ebit_sr["ebit-speed-roulette :4004<br/>roulette state machine"]
    end

    subgraph infra["Datastores + messaging"]
        direction TB
        ebit_db[("Postgres :5432<br/>ebit-db")]
        redis_cache[("Redis :6379<br/>cache · pwd=cache")]
        redis_bot[("Redis :6380<br/>bot · pwd=bot")]
        rabbitmq["RabbitMQ :5672<br/>vhost ft<br/>(stubbed)"]:::stub
    end

    %% Player traffic in
    user    -- "HTTPS :3000" --> ebit_fe
    ebit_fe -- "HTTP :4000"  --> ebit_api
    ebit_fe -- "WSS :4001"   --> ebit_rt

    %% Apps → infra
    ebit_api --> ebit_db
    ebit_rt  --> ebit_db
    ebit_sr  --> ebit_db
    ebit_api --> redis_cache
    ebit_rt  --> redis_cache
    ebit_sr  --> redis_cache
    ebit_api --> redis_bot
    ebit_api -. "disabled=true" .-> rabbitmq

    %% Inter-app RPC — AF-2 trace gap (red)
    ebit_api == "ExternalControllerClient<br/>NO traceparent" ==> ebit_sr
    ebit_sr  == "walletClient RPC<br/>NO traceparent"        ==> ebit_api
    ebit_api == "BalanceUpdated pub/sub<br/>NO traceparent"  ==> ebit_rt

    classDef stub fill:#eee,stroke:#999,color:#666,stroke-dasharray:5 5;
    linkStyle 11,12,13 stroke:#cc0000,stroke-width:2.5px;

Red thick edges = inter-app RPC over Redis pub/sub. traceparent is not propagated by Nest's microservice transport (AF-2 in ../weaknesses-register.md) — callee spans appear as orphan traces in Jaeger.

B. Admin / Ops path — ebit-admin-fe + ebit-bo + orphan ebit-bj

flowchart LR
    subgraph cli["Admin / Ops"]
        admin((Admin / Ops))
    end

    subgraph fe["Frontend"]
        ebit_admin_fe["ebit-admin-fe<br/>:5173 host / :3003 compose<br/>Vite + React 19 SPA"]
    end

    subgraph apps["Admin / Ops Nest apps"]
        direction TB
        ebit_api_admin["ebit-api :4000<br/>admin endpoints (PermissionGuard)"]
        ebit_bo["ebit-bo :4003<br/>backoffice + Swagger<br/>(separate process, ops tooling)"]
        ebit_bj["ebit-bj :4002<br/>orphan blackjack"]:::orphan
    end

    subgraph infra["Datastores"]
        direction TB
        ebit_db[("Postgres :5432<br/>ebit-db")]
        redis_cache[("Redis :6379<br/>cache · pwd=cache")]
    end

    %% Admin traffic in
    admin         -- "HTTPS :5173 / :3003" --> ebit_admin_fe
    ebit_admin_fe -- "HTTP :4000"          --> ebit_api_admin

    %% Apps → infra
    ebit_api_admin --> ebit_db
    ebit_bo        --> ebit_db
    ebit_api_admin --> redis_cache
    ebit_bo        --> redis_cache

    %% Orphan ebit-bj — runs in compose but no in-repo callers
    ebit_bj -. "no in-repo callers" .-> ebit_db
    ebit_bj -. "no in-repo callers" .-> redis_cache

    classDef orphan fill:#fff3cd,stroke:#a07000,color:#000;

ebit-api :4000 appears in both diagrams because the same Nest process serves both surfaces (the player REST handlers and the admin-guarded /admin/* controllers). ebit-bo is a separate Nest app on :4003 for ops tooling — it has its own Swagger and route tree, used by internal operators (not by ebit-admin-fe). ebit-bj runs on :4002 but no in-repo client calls it; dropbet's blackjack flows route through ebit-api's /casino/games/house/blackjack/* instead (AF-4 in ../weaknesses-register.md).

Legend: - Solid arrow — active traffic on the local stack today. - Dashed arrow — provisioned but no traffic. RabbitMQ is the only such edge: apps/api/src/fast-track/rabbitmq/fast-track.rmq.module.ts:8 hard-codes disabled = true and replaces the producer with a no-op stub (per project_otel_microservice_transport_gap.md and the inline compose comment that names all 11 silently-dropped call sites). - Red link — Redis pub/sub edge that does not propagate W3C traceparent. Spans on the callee side appear as orphan traces in Jaeger. Per memory project_otel_microservice_transport_gap.md, this is systemic to every @ExternalControllerClient edge — speed-roulette today, bj if ever wired up. - Yellow nodeebit-bj is architecturally orphaned (memory project_ebit_bj_orphan.md). The container runs and exposes :4002, but no in-repo FE reaches it — dropbet's blackjack UI calls ebit-api's /casino/games/house/blackjack/* instead.

Observability sidecars

graph LR
  apps[(All 5 NestJS apps<br/>+ ebit-fe + ebit-admin-fe)]
  apps -->|OTLP/HTTP :4318| otelc[otel-collector :4317/4318]

  filelog["/var/lib/docker/containers/<br/>JSON log files"] -->|filelog receiver| otelc

  otelc -->|otlphttp/jaeger| jaeger[Jaeger v2 :16686<br/>Badger backend]
  otelc -->|spanmetrics → prometheus| prom[Prometheus :9090]
  otelc -->|loki exporter| loki[Loki :3100]

  prom --> grafana[Grafana :3003]
  loki --> grafana
  jaeger --> grafana

The Collector is the single OTLP gateway — Jaeger no longer accepts OTLP directly; it's reached over the bridge network only. See tracing-flow.md for the full flow with processors and connectors.

Edge inventory

Every directed edge in the diagrams above, with protocol, auth, and trace state. "Traced" means a span chain reaches Jaeger with W3C traceparent intact across the hop.

From → To Protocol Port Auth Traced? Notes / cross-ref
Browser → ebit-fe HTTPS 3000 none (page) / access_token cookie (API proxy) yes — browser RUM via @vercel/otel ../flows/dropbet-sign-in.md §4.0
Browser → ebit-admin-fe HTTPS 3001 access_token cookie yes — admin-fe runs --require @opentelemetry/auto-instrumentations-node/build/src/register.js (compose NODE_OPTIONS) host-network mode — admin-fe SSR middleware calls NEXT_PUBLIC_API_URL during request processing
Browser → ebit-rt WSS 4001 socket_token (auth handshake / cookie / header) partial — per-emit send /events span only; no connection / handshake / message-handler spans ../flows/rt-websocket.md §6.1; gap also called out in ../audits/perf-trace-coverage-audit.md §WS
ebit-fe → ebit-api HTTP 4000 access_token cookie + JWT validation yes — @vercel/otel propagates traceparent, app.enableCors({...credentials:true}) reflects header ../flows/dropbet-sign-in.md §4.0
ebit-admin-fe → ebit-api HTTP 4000 access_token cookie + JWT + RolesGuard + (SuperAdmin) OtpGuard yes — same as ebit-fe ../flows/admin-sign-in.md
ebit-api → Postgres TCP (Prisma) 5432 user/password from DATABASE_URL yes — @prisma/instrumentation 6.5.0 emits prisma:client:operation + prisma:engine:db_query (per project_otel_spanmetrics_connector.md) every Prisma call wrapped
ebit-rt → Postgres TCP (Prisma) 5432 same yes reads User, writes UserSession
ebit-bo / ebit-sr / ebit-bj → Postgres TCP 5432 same yes for sr; yes-via---require for bj/bo bj/bo main.ts doesn't share the pre-imports chain — compose adds NODE_OPTIONS=--require .../register.js to bootstrap OTel
ebit-api → Redis (cache :6379) RESP 6379 password cache yes — @opentelemetry/instrumentation-ioredis per-command spans BullMQ enqueue surfaces here as EVALSHA
ebit-rt → Redis (cache) RESP 6379 password cache yes ONLINE_USERS_KEY zadd / pub/sub subscribe
ebit-api → Redis (bot :6380) RESP 6380 password bot yes bot fleet queues — separate Redis to isolate bot traffic from cache hot path
ebit-api → RabbitMQ AMQP 5672 rabbitmq/rabbitmq vhost ft n/a — stubbed at disabled=true producer is a no-op (fast-track/rabbitmq/fast-track.rmq.module.ts:8); 4× emitTransaction in bet.service + 7× emitBonus in promo-effect.service silently drop
ebit-api → ebit-sr Redis pub/sub (Nest microservice transport via @ExternalControllerClient) (uses cache Redis) internal-only NO — see project_otel_microservice_transport_gap.md callee spans are orphan traces in Jaeger
ebit-sr → ebit-api (walletClient) Redis pub/sub (uses cache Redis) internal-only NO — same gap three-hop bet trace appears as three uncorrelated traces
ebit-api → ebit-rt Redis pub/sub channel server_channel_event.* (e.g. BalanceUpdated) (uses cache Redis) internal-only partial — publish span on the api side; rt's MessagePattern handler is not auto-instrumented, then the send /events emit is its own root span ../flows/dropbet-bet-place.md §4.3 (publish), ../flows/rt-websocket.md (rt side)
All NestJS apps → otel-collector OTLP/HTTP 4318 none (internal bridge net) n/a (this is the trace transport itself) OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
ebit-fe + ebit-admin-fe (SSR) → otel-collector OTLP/HTTP 4318 (bridge) none n/a server-side spans
ebit-fe (browser) → otel-collector OTLP/HTTP host-published 4318 none — CORS allowlist localhost:{3000,3001,3003} n/a NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 (host port; browser can't reach docker DNS)
Docker daemon → otel-collector filelog (file scrape) n/a reads /var/lib/docker/containers as root n/a (logs only) bridges EvoLogger/winston records that bypass the OTel logs SDK (per project_evologger_trace_correlation.md)
otel-collector → Jaeger OTLP/HTTP 4318 (bridge) tls.insecure yes (this is the trace sink) Jaeger v2.17.0 with Badger backend at /opt/jaeger-data
otel-collector → Prometheus scrape :8889/metrics 8889 none metrics only spanmetrics-derived series (traces_spanmetrics_calls_total, _duration_milliseconds_bucket)
otel-collector → Loki HTTP push 3100 none logs only both OTLP-bridged pino + filelog winston records

Traffic mode summary

  • REST hot path — Browser → ebit-fe → ebit-api → Postgres + Redis. Fully traced end-to-end as of task #27 (browser RUM) + the manual AuthService.login span work.
  • Websocket hot path — Browser → ebit-rt over WSS, server → client pushes triggered by Redis pub/sub from ebit-api. Trace coverage is partial: per-emit send /events spans exist, but connection/handshake/auth-RPC are not part of the trace tree (see ../flows/rt-websocket.md §6.1).
  • Game-server fan-out — ebit-api proxies speed-roulette via @ExternalControllerClient. Three Jaeger traces, no parent linkage. Same architecture would apply to ebit-bj if anyone wired a proxy.
  • Async work — BullMQ on the cache Redis. Producer's EVALSHA span is parented to the request, but the consumer starts an orphan trace (queue context not propagated — also called out in ../audits/perf-trace-coverage-audit.md).
  • External integration — RabbitMQ ft vhost is provisioned but stubbed; all 11 producer call sites are no-ops today.