Running the backend
The same image and codebase expose three production entrypoints plus a CLI.
| Process | Entry | npm/yarn script | Role |
|---|---|---|---|
| API | src/main.ts | yarn start:api / yarn dev | HTTP, Swagger when not production, WebSocket adapter |
| Worker | src/main.worker.ts | yarn start:worker | Bull processors and OutboxDispatcherService |
| Scheduler | src/main.scheduler.ts | yarn start:scheduler | Cron ticks that enqueue work or run maintenance |
| CLI | src/cli.ts | yarn seed:demo | nestjs-command |
Production equivalents: yarn start:prod, yarn start:worker:prod, yarn start:scheduler:prod (node dist/main, dist/main.worker, dist/main.scheduler).
docker-compose.yml runs API, worker, and scheduler from one image with different commands. Compose comments require yarn migrate:deploy before the API health check can pass. Containers force APP_LOG_LEVEL default info even if .env says debug.
What each process loads
AppModule imports CoreModule, the five area modules, and IntegrationsModule. WorkerModule also imports OutboxDispatchModule. The dispatcher is not loaded by the API module.
EventEmitterModule is registered from common infrastructure, which both API and worker import. A listener runs only in the process that loaded its module. Outbox handlers must be loaded by the worker. An in-process emit on the API is invisible to a worker-only listener.
PartitionMaintenanceService is loaded from the worker module, so its cron runs on the worker, not on the scheduler process.
AI_CONTEXT.md says the scheduler must stay single-replica. Whether the deploy workflow enforces that was not confirmed. See Open questions.
Health
GET /health is version-neutral and public. Prometheus metrics are at /metrics and bypass the JWT guard. Confirm a network policy so /metrics is not public. That policy is unknown from the application code alone.