spaxel/docker-compose.yml
jedarden 3f2962f945 feat(deploy): Docker packaging with multi-stage build and compose orchestration
- Dockerfile: golang:1.23-bookworm builder → distroless/static-debian12:nonroot
- docker-compose.yml: host networking (required for mDNS), Traefik labels, resource limits
- VERSION: 0.1.0 for image tagging
- .dockerignore: excludes docs, build artifacts, IDE files
- .gitignore: standard Go/ESP-IDF ignores

Key decisions:
- Host networking required: Docker bridge blocks mDNS multicast 224.0.0.251
- distroless/static-debian12:nonroot: no shell, minimal attack surface, UID 65532
- Firmware via volume mount: users provide their own binaries for OTA
- Traefik labels disabled by default: enable SPAXEL_TRAEFIK_ENABLE=true for TLS

Complete: Phase 1 Docker packaging — all foundation items now done
Remaining: Phase 2 signal processing (baseline, deltaRMS, Fresnel zones)
2026-03-26 07:46:15 -04:00

87 lines
3 KiB
YAML

# Spaxel Docker Compose
# Production deployment configuration
#
# IMPORTANT: network_mode: host is REQUIRED for mDNS to work.
# mDNS uses multicast address 224.0.0.251 (link-local), which Docker
# bridge networking blocks. With host networking, the container shares
# the host's network interfaces and mDNS multicasts reach the LAN.
#
# Side effect: 'ports' mapping is ignored in host mode — port 8080 is
# directly exposed on the host.
services:
spaxel:
build:
context: .
dockerfile: Dockerfile
args:
VERSION: ${VERSION:-dev}
# Alternative: use pre-built image
# image: ghcr.io/spaxel/spaxel:latest
# Host networking required for mDNS discovery by ESP32 nodes
network_mode: host
# Alternative (if host mode is not desired): disable mDNS and require
# nodes to use cached ms_ip NVS key (manual IP entry during provisioning).
# Set SPAXEL_MDNS_ENABLED=false to skip the mDNS advertisement entirely.
volumes:
- spaxel-data:/data # SQLite, baselines, floor plans, CSI buffer
- ./firmware:/firmware:ro # Firmware binaries for OTA (read-only)
environment:
# Timezone - required for correct diurnal baseline hours
TZ: ${TZ:-UTC}
# mDNS service name (must match firmware ms_mdns NVS key)
SPAXEL_MDNS_NAME: ${SPAXEL_MDNS_NAME:-spaxel}
# Set to false if using bridge networking (nodes must use cached IP)
SPAXEL_MDNS_ENABLED: ${SPAXEL_MDNS_ENABLED:-true}
# Optional MQTT integration (uncomment to enable)
# SPAXEL_MQTT_BROKER: mqtt://homeassistant.local:1883
# SPAXEL_MQTT_USERNAME: mosquitto
# SPAXEL_MQTT_PASSWORD: secret
# Debug logging (uncomment for troubleshooting)
# SPAXEL_LOG_LEVEL: debug
restart: unless-stopped
# Allow full 30s graceful shutdown
stop_grace_period: 35s
ulimits:
nofile:
soft: 4096 # One fd per node connection + SQLite handles
hard: 8192
healthcheck:
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
deploy:
resources:
limits:
memory: 512m # Increase to 1g for 16+ node fleets
cpus: "2.0"
reservations:
memory: 128m
cpus: "0.5"
# Traefik labels for reverse proxy with TLS
# Uncomment and customize for your Traefik setup
labels:
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
# - "traefik.http.routers.spaxel.rule=Host(`spaxel.example.com`)"
# - "traefik.http.routers.spaxel.entrypoints=websecure"
# - "traefik.http.routers.spaxel.tls.certresolver=letsencrypt"
# - "traefik.http.services.spaxel.loadbalancer.server.port=8080"
# # Extend Traefik timeout for long-lived WebSocket connections
# - "traefik.http.routers.spaxel.middlewares=spaxel-ws-timeout"
# - "traefik.http.middlewares.spaxel-ws-timeout.headers.respondingTimeouts.readTimeout=3600s"
volumes:
spaxel-data:
driver: local