Deployment topology¶
Databús runs entirely in Docker Compose. Two compose files share the same service names and images but differ in process targets, networking, and TLS.
Compose services summary¶
| Service | Process | Queue / port | Notes |
|---|---|---|---|
orchestrator |
Django + Daphne ASGI | :8000 | HTTP + WebSocket |
realtime-engine |
celery worker -Q realtime_engine |
realtime_engine |
Hosts MQTT bootstep |
schedule-engine |
celery worker -Q schedule_engine |
schedule_engine |
GTFS-RT projection |
scheduler |
celery beat |
— | Fires periodic tasks |
state |
Redis 7 | :6379 | Authoritative state |
database |
PostgreSQL + PostGIS | :5432 | Domain storage |
telemetry-broker |
NanoMQ 0.24.9 | :1883 | MQTT broker |
message-broker |
RabbitMQ 4 | :5672 / :15672 | Task queues + AMQP |
analytics-engine |
Prefect 3 | :4200 | Batch analytics |
task-monitoring |
Flower 2 | :5555 | Celery dashboard |
user-interface |
Nuxt | :3000 | Web frontend |
docs |
nginx | :80 | Prod only |
The MQTT single-consumer gate¶
Only one service should subscribe to the NanoMQ broker. The gate is controlled by the MQTT_CONSUMER_ENABLED environment variable:
# compose.dev.yml — realtime-engine only
realtime-engine:
environment:
- MQTT_CONSUMER_ENABLED=true
- MQTT_HOST=telemetry-broker
- MQTT_PORT=1883
Every Celery worker process imports MQTTConsumerStep from backend/databus/celery.py, but the step checks MQTT_CONSUMER_ENABLED at boot and silently skips if it is not "true". This means schedule-engine and scheduler workers never open an MQTT connection, even though the step is registered globally.
Failing to set this var to exactly one worker causes either no subscription (if unset everywhere) or duplicate subscriptions that trigger MQTT session conflict and reconnect storms. See ../operations/troubleshooting.md for the symptom and the client_id fix (commit 452d4f4).
Queue routing¶
The two Celery workers drain separate named queues, keeping concerns isolated:
realtime_engine queue → realtime-engine worker
- process_position_update
- run_lifecycle_event
- scan_stale_runs
schedule_engine queue → schedule-engine worker
- build_vehicle_positions
- build_trip_updates
- build_alerts
Tasks are routed by the queue= argument on the @shared_task decorator in the respective tasks.py modules.
Development compose¶
compose.dev.yml (name: databus-dev) runs everything with:
- Bind-mounted source code (
./backend:/app) so changes are reflected without rebuilds. - Host-exposed ports for direct access (Redis on
:6379, RabbitMQ management on:15672, etc.). - No Traefik — services are reached on
localhost:<port>. - A shared
backend_venvnamed volume so the uv virtualenv is built once.
Production compose¶
compose.prod.yml (name: databus-prod) changes:
- All services join the
internalDocker network; only services that need external access additionally jointraefik_proxy. - Traefik handles TLS termination and subdomain routing:
$ORCHESTRATOR_DOMAIN→orchestrator:8000$UI_DOMAIN→user-interface:3000$MQTT_DOMAIN(TCP, port 8883) → TLS-terminated, forwarded totelemetry-broker:1883$RABBITMQ_DOMAIN→ RabbitMQ management UI (port 15672)$ANALYTICS_DOMAIN→ Prefect (port 4200)$FLOWER_DOMAIN→ Flower (port 5555)$DOCS_DOMAIN→ nginx servingdocs/site/
restart: unless-stoppedon all services.security_opt: no-new-privileges:trueon all services.- Redis requires a password (
REDIS_PASSWORD). - The
docsservice is only present in prod (nginx +docs/site/volume mount). - No bind-mounted source code — images are built from the Dockerfile
prodtarget.
Health checks¶
| Service | Health check |
|---|---|
database |
pg_isready -U $DB_USER -d $DB_NAME |
state |
redis-cli ping (prod: -a $REDIS_PASSWORD) |
message-broker |
rabbitmq-diagnostics status |
The realtime-engine and schedule-engine workers wait for database, state, and message-broker to be healthy before starting. The orchestrator waits for database and state.
Environment variables¶
Core variables (full reference: ../operations/configuration.md):
| Variable | Default | Used by |
|---|---|---|
MQTT_CONSUMER_ENABLED |
false |
realtime-engine only (set to true) |
MQTT_HOST |
telemetry-broker |
realtime-engine bootstep |
MQTT_PORT |
1883 |
realtime-engine bootstep |
REDIS_HOST |
state |
All workers |
REDIS_PORT |
6379 |
All workers |
SECRET_KEY |
— | orchestrator |
DB_NAME / DB_USER / DB_PASSWORD |
— | All (ORM) |
RABBITMQ_USER / RABBITMQ_PASS |
guest/guest |
Celery broker URL |