# Production stack — deployed to the Unraid Docker host as a Portainer stack. # # Differences from docker-compose.yml (the local dev file): # * No build: sections. ops/deploy.py builds the four images on the Unraid daemon # first; this file only references them by tag. # * No source bind mounts and no uvicorn --reload. Code lives in the image. # * No env_file. Everything is set inline — there are no .env files on the host. # * Only the frontend publishes a host port. Postgres and MQTT stay on the # internal bms_net network, as the README's reverse-proxy note recommends. # * Postgres data is bind-mounted to /mnt/user/appdata/bms/db so it lands on the # Unraid array rather than growing inside docker.img. # # Deploy / redeploy with: python3 ops/deploy.py services: # ── MQTT Broker ────────────────────────────────────────────────── mqtt: image: bms-mqtt:latest container_name: bms_mqtt restart: unless-stopped networks: [bms_net] healthcheck: test: ["CMD-SHELL", "mosquitto_sub -t '$$SYS/#' -C 1 -i healthcheck -W 3"] interval: 10s timeout: 5s retries: 5 # ── PostgreSQL + TimescaleDB ───────────────────────────────────── db: image: timescale/timescaledb:latest-pg16 container_name: bms_db restart: unless-stopped networks: [bms_net] environment: POSTGRES_USER: dcim POSTGRES_PASSWORD: dcim_pass POSTGRES_DB: dcim volumes: - /mnt/user/appdata/bms/db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U dcim -d dcim"] interval: 10s timeout: 5s retries: 5 # ── FastAPI backend ────────────────────────────────────────────── backend: image: bms-backend:latest container_name: bms_backend restart: unless-stopped networks: [bms_net] environment: DATABASE_URL: postgresql+asyncpg://dcim:dcim_pass@db:5432/dcim MQTT_HOST: mqtt MQTT_PORT: "1883" CORS_ORIGINS: "[]" DEBUG: "false" depends_on: db: condition: service_healthy mqtt: condition: service_healthy healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:8000/api/health || exit 1"] interval: 10s timeout: 5s retries: 10 start_period: 20s # ── Simulator bots (seed first, then run bots) ─────────────────── simulators: image: bms-simulators:latest container_name: bms_simulators restart: unless-stopped networks: [bms_net] environment: MQTT_HOST: mqtt MQTT_PORT: "1883" DATABASE_URL: postgresql://dcim:dcim_pass@db:5432/dcim SEED_MINUTES: "30" depends_on: db: condition: service_healthy mqtt: condition: service_healthy backend: condition: service_healthy # ── Next.js frontend (the only publicly reachable service) ─────── frontend: image: bms-frontend:latest container_name: bms_frontend restart: unless-stopped networks: [bms_net] ports: - "5646:5646" environment: PORT: "5646" HOSTNAME: "0.0.0.0" NODE_ENV: production BACKEND_INTERNAL_URL: http://backend:8000 depends_on: - backend networks: # The Unraid daemon's default address pools are fully subnetted — 24 existing # stacks have claimed every 172.17-172.31/16 and 192.168.x/20 slot, so letting # Docker auto-allocate fails with "all predefined address pools have been fully # subnetted". An explicit subnet bypasses the allocator entirely. # 172.31.42.0/24 is free: the only other 172.31 tenant is wg0 on 172.31.200.0/24. bms_net: driver: bridge ipam: config: - subnet: 172.31.42.0/24