ops/deploy.py ships each service's build context to the remote Docker daemon's /build endpoint and pushes docker-compose.prod.yml as a Portainer stack — there is no docker CLI in WSL and Unraid's SSH is closed. The prod compose file drops the dev bind mounts and uvicorn --reload, publishes only the frontend port (8000 is taken by Portainer's Edge tunnel), and pins bms_net to 172.31.42.0/24 because the host's default address pools are fully subnetted. Mosquitto's config is baked into an image since the repo is not checked out on the host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
116 lines
4 KiB
YAML
116 lines
4 KiB
YAML
# 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
|