Hub Deployment
The hub is the central component of BirdNET-NG: an API gateway, MQTT ingester, job queue manager, and web UI host. It runs as an 8+ container Docker Compose stack behind a Traefik reverse proxy (9 with split mode).
What the Hub Does
- API gateway (Fastify) — serves the REST API for web and mobile clients, handles JWT and API key auth
- MQTT ingester — receives audio chunks and telemetry from satellites via Mosquitto
- Job queue — dispatches BirdNET inference jobs to workers via Redis + BullMQ
- Web UI — serves the React SPA via nginx, proxying
/api/*to the hub process - Database — PostgreSQL 17 with multi-tenant row-level isolation
- Storage — MinIO for S3-compatible audio and image blob storage
Docker Compose Deployment
Docker Compose is the primary and recommended deployment method.
Containers
| Container | Image | Purpose |
|---|---|---|
| postgres | PostgreSQL 17 | Multi-tenant data store |
| redis | Redis 7 | BullMQ job queue backend |
| mosquitto | Mosquitto 2.x | MQTT broker (dynamic security plugin) |
| minio | MinIO | S3-compatible audio + image storage |
| api | Node.js | Hub process (API gateway) |
| dispatcher | Node.js | Background workers (split mode only: MQTT ingester, monitors, webhooks, image worker) |
| web | nginx | React SPA, proxies /api/* to hub |
| docs | Node.js | Documentation site (Express + markdown-it) |
| worker | Python 3.11 | BirdNET inference (scalable) |
Networks
- traefik (external) — Traefik routes HTTPS + WSS traffic
- backend (internal) — Inter-container communication
Storage
All persistent data uses bind mounts under BNG_VOLUMES_ROOT (default: ./storage/):
storage/
├── postgres/ # Database files
├── redis/ # Redis AOF
├── mosquitto/ # Broker data + dynamic security DB
└── minio/ # Audio blobs + species images
Commands
# Start full stack (default mode — single hub process)
docker compose up -d
# Rebuild after code changes
docker compose up -d --build
# View logs
docker compose logs -f api
# Stop everything
docker compose down
Hub Modes (HUB_MODE)
The hub can run in three modes, controlled by the HUB_MODE environment variable:
| Mode | Description |
|---|---|
full (default) |
Single process running the API server and all background workers. No Redis pub/sub bridge needed. This is the original behavior. |
api |
Stateless Fastify HTTP + WebSocket server only. Can be scaled horizontally with replicas. Uses a lightweight MqttConfigPusher to push config to satellites via MQTT. |
dispatcher |
Background worker only: MQTT ingester, satellite monitor, detection watcher, webhook dispatcher, species image worker. Must run as a single instance. |
In full mode, everything runs in one process exactly as before. In split mode, api and dispatcher run as separate containers and communicate via Redis pub/sub (e.g., detection alerts are published by the dispatcher and delivered to WebSocket clients by the API).
Default Mode (unchanged)
Don't set HUB_MODE (or set it to full). The standard docker compose up -d starts a single hub process that handles everything:
docker compose up -d
Split Mode Deployment
Split mode separates the stateless API from the background workers, allowing you to scale the HTTP/WebSocket layer independently.
- Set
HUB_MODE=apiin your.envfile. - Start the stack with the
splitprofile:
docker compose --profile split up -d
This starts two hub containers:
- api (
HUB_MODE=api) — handles HTTP requests and WebSocket connections - dispatcher (
HUB_MODE=dispatcher) — runs MQTT ingestion and all background workers
- Scale the API layer:
docker compose --profile split up -d --scale api=3
The dispatcher must remain a single instance (it owns MQTT subscriptions, satellite monitoring, and the species image download queue). The API containers are stateless and safe to scale behind Traefik.
Note: Each API instance maintains its own MQTT connections (for credential provisioning and config push). These connections auto-reconnect and will wait up to 10 seconds for reconnection if temporarily lost. No sticky sessions or shared state is required — all API instances are fully independent.
Configuration
All runtime configuration is via .env (see .env.example for the full template). No hardcoded secrets.
FQDNs
Four FQDNs are required, each configured in .env:
| Variable | Purpose |
|---|---|
BNG_APP_FQDN |
Web UI + API (HTTPS, nginx -> hub) |
BNG_MQTT_FQDN |
Mosquitto broker (WSS via HTTPS :443) |
BNG_S3_FQDN |
MinIO console (HTTPS) |
BNG_DOCS_FQDN |
Documentation site (HTTPS) |
Key Environment Variables
| Variable | Description |
|---|---|
JWT_SECRET |
Secret for signing JWT session tokens (required) |
PLATFORM_ADMIN_EMAILS |
Comma-separated email addresses that get platform admin access |
HUB_INTERNAL_API_KEY |
Backwards-compatible machine access key (optional) |
Traefik Configuration
Four routes are needed across two entrypoints:
| Entrypoint | Port | Protocol | Service |
|---|---|---|---|
| websecure | 443 | HTTPS | Web UI + API (BNG_APP_FQDN) |
| websecure | 443 | HTTPS (WSS) | MQTT broker via WebSocket (BNG_MQTT_FQDN) |
| websecure | 443 | HTTPS | MinIO console (BNG_S3_FQDN) |
| websecure | 443 | HTTPS | Documentation site (BNG_DOCS_FQDN) |
The docker-compose.yml includes Traefik labels for automatic routing. No services expose ports directly to the host; everything goes through Traefik.
Database Migrations
Migrations run automatically on hub startup (see packages/hub/src/db/migrate.ts). There are currently 56 migrations (001-056); see ARCHITECTURE.md for the full list.
To run migrations manually:
docker compose exec api node packages/hub/dist/db/migrate.js
Scaling Inference Workers
Workers are stateless and can be scaled independently of the hub:
docker compose up --scale worker=4 -d
See the Workers page for detailed worker deployment options.
Updating
To update the hub and all services:
cd ~/birdnet-ng
git pull
docker compose --profile split up -d --build --scale api=2 --scale worker=2
This rebuilds all containers with the latest code and restarts them. Database migrations run automatically on API startup. No data loss — PostgreSQL, Redis, and MinIO volumes are persistent.
For zero-downtime updates with scaled API instances, the load balancer (Traefik) handles rolling restarts automatically.