Add Unraid deployment via the Portainer API
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>
This commit is contained in:
parent
d4a104be9d
commit
de9b8c51bd
5 changed files with 510 additions and 22 deletions
116
docker-compose.prod.yml
Normal file
116
docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue