services: # ========================================================================== # MQTT Broker - Eclipse Mosquitto (optional, use --profile mqtt) # Most users will connect to an external MQTT broker instead # ========================================================================== mqtt: image: eclipse-mosquitto:2 container_name: meshcore-mqtt profiles: - all - mqtt restart: unless-stopped ports: - "${MQTT_EXTERNAL_PORT:-1883}:1883" - "${MQTT_WS_PORT:-9001}:9001" volumes: # - ./etc/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro - mosquitto_data:/mosquitto/data - mosquitto_log:/mosquitto/log healthcheck: test: ["CMD", "mosquitto_sub", "-t", "$$SYS/#", "-C", "1", "-i", "healthcheck", "-W", "3"] interval: 30s timeout: 10s retries: 3 start_period: 10s # ========================================================================== # Interface Receiver - MeshCore device to MQTT bridge (events) # ========================================================================== interface-receiver: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-interface-receiver profiles: - all - receiver restart: unless-stopped devices: - "${SERIAL_PORT:-/dev/ttyUSB0}:${SERIAL_PORT:-/dev/ttyUSB0}" user: root # Required for device access environment: - LOG_LEVEL=${LOG_LEVEL:-INFO} - MQTT_HOST=${MQTT_HOST:-mqtt} - MQTT_PORT=${MQTT_PORT:-1883} - MQTT_USERNAME=${MQTT_USERNAME:-} - MQTT_PASSWORD=${MQTT_PASSWORD:-} - MQTT_PREFIX=${MQTT_PREFIX:-meshcore} - MQTT_TLS=${MQTT_TLS:-false} - MQTT_TRANSPORT=${MQTT_TRANSPORT:-tcp} - MQTT_WS_PATH=${MQTT_WS_PATH:-/mqtt} - SERIAL_PORT=${SERIAL_PORT:-/dev/ttyUSB0} - SERIAL_BAUD=${SERIAL_BAUD:-115200} - NODE_ADDRESS=${NODE_ADDRESS:-} command: ["interface", "receiver"] healthcheck: test: ["CMD", "meshcore-hub", "health", "interface"] interval: 30s timeout: 10s retries: 3 start_period: 30s # ========================================================================== # Interface Sender - MQTT to MeshCore device bridge (commands) # ========================================================================== interface-sender: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-interface-sender profiles: - all - sender restart: unless-stopped devices: - "${SERIAL_PORT_SENDER:-/dev/ttyUSB1}:${SERIAL_PORT_SENDER:-/dev/ttyUSB1}" user: root # Required for device access environment: - LOG_LEVEL=${LOG_LEVEL:-INFO} - MQTT_HOST=${MQTT_HOST:-mqtt} - MQTT_PORT=${MQTT_PORT:-1883} - MQTT_USERNAME=${MQTT_USERNAME:-} - MQTT_PASSWORD=${MQTT_PASSWORD:-} - MQTT_PREFIX=${MQTT_PREFIX:-meshcore} - MQTT_TLS=${MQTT_TLS:-false} - MQTT_TRANSPORT=${MQTT_TRANSPORT:-tcp} - MQTT_WS_PATH=${MQTT_WS_PATH:-/mqtt} - SERIAL_PORT=${SERIAL_PORT_SENDER:-/dev/ttyUSB1} - SERIAL_BAUD=${SERIAL_BAUD:-115200} - NODE_ADDRESS=${NODE_ADDRESS_SENDER:-} command: ["interface", "sender"] healthcheck: test: ["CMD", "meshcore-hub", "health", "interface"] interval: 30s timeout: 10s retries: 3 start_period: 30s # ========================================================================== # Interface Mock Receiver - For testing without real devices # ========================================================================== interface-mock-receiver: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-interface-mock-receiver profiles: - all - mock restart: unless-stopped environment: - LOG_LEVEL=${LOG_LEVEL:-INFO} - MQTT_HOST=${MQTT_HOST:-mqtt} - MQTT_PORT=${MQTT_PORT:-1883} - MQTT_USERNAME=${MQTT_USERNAME:-} - MQTT_PASSWORD=${MQTT_PASSWORD:-} - MQTT_PREFIX=${MQTT_PREFIX:-meshcore} - MQTT_TLS=${MQTT_TLS:-false} - MQTT_TRANSPORT=${MQTT_TRANSPORT:-tcp} - MQTT_WS_PATH=${MQTT_WS_PATH:-/mqtt} - MOCK_DEVICE=true - NODE_ADDRESS=${NODE_ADDRESS:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef} command: ["interface", "receiver", "--mock"] healthcheck: test: ["CMD", "meshcore-hub", "health", "interface"] interval: 30s timeout: 10s retries: 3 start_period: 10s # ========================================================================== # Collector - MQTT subscriber and database storage # ========================================================================== collector: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-collector profiles: - all - core restart: unless-stopped depends_on: db-migrate: condition: service_completed_successfully volumes: - hub_data:/data - ${SEED_HOME:-./seed}:/seed environment: - LOG_LEVEL=${LOG_LEVEL:-INFO} - MQTT_HOST=${MQTT_HOST:-mqtt} - MQTT_PORT=${MQTT_PORT:-1883} - MQTT_USERNAME=${MQTT_USERNAME:-} - MQTT_PASSWORD=${MQTT_PASSWORD:-} - MQTT_PREFIX=${MQTT_PREFIX:-meshcore} - MQTT_TLS=${MQTT_TLS:-false} - MQTT_TRANSPORT=${MQTT_TRANSPORT:-tcp} - MQTT_WS_PATH=${MQTT_WS_PATH:-/mqtt} - COLLECTOR_INGEST_MODE=${COLLECTOR_INGEST_MODE:-native} - COLLECTOR_LETSMESH_DECODER_ENABLED=${COLLECTOR_LETSMESH_DECODER_ENABLED:-true} - COLLECTOR_LETSMESH_DECODER_COMMAND=${COLLECTOR_LETSMESH_DECODER_COMMAND:-meshcore-decoder} - COLLECTOR_LETSMESH_DECODER_KEYS=${COLLECTOR_LETSMESH_DECODER_KEYS:-} - COLLECTOR_LETSMESH_DECODER_TIMEOUT_SECONDS=${COLLECTOR_LETSMESH_DECODER_TIMEOUT_SECONDS:-2.0} - DATA_HOME=/data - SEED_HOME=/seed # Webhook configuration - WEBHOOK_ADVERTISEMENT_URL=${WEBHOOK_ADVERTISEMENT_URL:-} - WEBHOOK_ADVERTISEMENT_SECRET=${WEBHOOK_ADVERTISEMENT_SECRET:-} - WEBHOOK_MESSAGE_URL=${WEBHOOK_MESSAGE_URL:-} - WEBHOOK_MESSAGE_SECRET=${WEBHOOK_MESSAGE_SECRET:-} - WEBHOOK_CHANNEL_MESSAGE_URL=${WEBHOOK_CHANNEL_MESSAGE_URL:-} - WEBHOOK_CHANNEL_MESSAGE_SECRET=${WEBHOOK_CHANNEL_MESSAGE_SECRET:-} - WEBHOOK_DIRECT_MESSAGE_URL=${WEBHOOK_DIRECT_MESSAGE_URL:-} - WEBHOOK_DIRECT_MESSAGE_SECRET=${WEBHOOK_DIRECT_MESSAGE_SECRET:-} - WEBHOOK_TIMEOUT=${WEBHOOK_TIMEOUT:-10.0} - WEBHOOK_MAX_RETRIES=${WEBHOOK_MAX_RETRIES:-3} - WEBHOOK_RETRY_BACKOFF=${WEBHOOK_RETRY_BACKOFF:-2.0} # Data retention and cleanup configuration - DATA_RETENTION_ENABLED=${DATA_RETENTION_ENABLED:-true} - DATA_RETENTION_DAYS=${DATA_RETENTION_DAYS:-30} - DATA_RETENTION_INTERVAL_HOURS=${DATA_RETENTION_INTERVAL_HOURS:-24} - NODE_CLEANUP_ENABLED=${NODE_CLEANUP_ENABLED:-true} - NODE_CLEANUP_DAYS=${NODE_CLEANUP_DAYS:-7} command: ["collector"] healthcheck: test: ["CMD", "meshcore-hub", "health", "collector"] interval: 30s timeout: 10s retries: 3 start_period: 10s # ========================================================================== # API Server - REST API for querying data and sending commands # ========================================================================== api: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-api profiles: - all - core restart: unless-stopped depends_on: db-migrate: condition: service_completed_successfully collector: condition: service_started ports: - "${API_PORT:-8000}:8000" volumes: - hub_data:/data environment: - LOG_LEVEL=${LOG_LEVEL:-INFO} - MQTT_HOST=${MQTT_HOST:-mqtt} - MQTT_PORT=${MQTT_PORT:-1883} - MQTT_USERNAME=${MQTT_USERNAME:-} - MQTT_PASSWORD=${MQTT_PASSWORD:-} - MQTT_PREFIX=${MQTT_PREFIX:-meshcore} - MQTT_TLS=${MQTT_TLS:-false} - MQTT_TRANSPORT=${MQTT_TRANSPORT:-tcp} - MQTT_WS_PATH=${MQTT_WS_PATH:-/mqtt} - DATA_HOME=/data - API_HOST=0.0.0.0 - API_PORT=8000 - API_READ_KEY=${API_READ_KEY:-} - API_ADMIN_KEY=${API_ADMIN_KEY:-} - METRICS_ENABLED=${METRICS_ENABLED:-true} - METRICS_CACHE_TTL=${METRICS_CACHE_TTL:-60} command: ["api"] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] interval: 30s timeout: 10s retries: 3 start_period: 10s # ========================================================================== # Web Dashboard - Web interface for network visualization # ========================================================================== web: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-web profiles: - all - core restart: unless-stopped depends_on: api: condition: service_healthy ports: - "${WEB_PORT:-8080}:8080" volumes: - ${CONTENT_HOME:-./content}:/content:ro environment: - LOG_LEVEL=${LOG_LEVEL:-INFO} - API_BASE_URL=http://api:8000 # Use ADMIN key to allow write operations from admin interface # Falls back to READ key if ADMIN key is not set - API_KEY=${API_ADMIN_KEY:-${API_READ_KEY:-}} - WEB_HOST=0.0.0.0 - WEB_PORT=8080 - WEB_THEME=${WEB_THEME:-dark} - WEB_LOCALE=${WEB_LOCALE:-en} - WEB_DATETIME_LOCALE=${WEB_DATETIME_LOCALE:-en-US} - WEB_ADMIN_ENABLED=${WEB_ADMIN_ENABLED:-false} - NETWORK_NAME=${NETWORK_NAME:-MeshCore Network} - NETWORK_CITY=${NETWORK_CITY:-} - NETWORK_COUNTRY=${NETWORK_COUNTRY:-} - NETWORK_RADIO_CONFIG=${NETWORK_RADIO_CONFIG:-} - NETWORK_CONTACT_EMAIL=${NETWORK_CONTACT_EMAIL:-} - NETWORK_CONTACT_DISCORD=${NETWORK_CONTACT_DISCORD:-} - NETWORK_CONTACT_GITHUB=${NETWORK_CONTACT_GITHUB:-} - NETWORK_CONTACT_YOUTUBE=${NETWORK_CONTACT_YOUTUBE:-} - NETWORK_WELCOME_TEXT=${NETWORK_WELCOME_TEXT:-} - CONTENT_HOME=/content - TZ=${TZ:-UTC} - COLLECTOR_LETSMESH_DECODER_KEYS=${COLLECTOR_LETSMESH_DECODER_KEYS:-} # Feature flags (set to false to disable specific pages) - FEATURE_DASHBOARD=${FEATURE_DASHBOARD:-true} - FEATURE_NODES=${FEATURE_NODES:-true} - FEATURE_ADVERTISEMENTS=${FEATURE_ADVERTISEMENTS:-true} - FEATURE_MESSAGES=${FEATURE_MESSAGES:-true} - FEATURE_MAP=${FEATURE_MAP:-true} - FEATURE_MEMBERS=${FEATURE_MEMBERS:-true} - FEATURE_PAGES=${FEATURE_PAGES:-true} command: ["web"] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"] interval: 30s timeout: 10s retries: 3 start_period: 10s # ========================================================================== # Database Migrations - Run Alembic migrations # ========================================================================== db-migrate: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-db-migrate profiles: - all - core - migrate restart: "no" volumes: - hub_data:/data environment: - DATA_HOME=/data command: ["db", "upgrade"] # ========================================================================== # Seed Data - Import node_tags.yaml and members.yaml from SEED_HOME # NOTE: This is NOT run automatically. Use --profile seed to run explicitly. # Since tags are now managed via the admin UI, automatic seeding would # overwrite user changes. # ========================================================================== seed: image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest} build: context: . dockerfile: Dockerfile container_name: meshcore-seed profiles: - seed restart: "no" volumes: - hub_data:/data - ${SEED_HOME:-./seed}:/seed:ro environment: - DATA_HOME=/data - SEED_HOME=/seed - LOG_LEVEL=${LOG_LEVEL:-INFO} # Imports both node_tags.yaml and members.yaml if they exist command: ["collector", "seed"] # ========================================================================== # Prometheus - Metrics collection and monitoring (optional, use --profile metrics) # ========================================================================== prometheus: image: prom/prometheus:latest container_name: meshcore-prometheus profiles: - all - metrics restart: unless-stopped depends_on: api: condition: service_healthy ports: - "${PROMETHEUS_PORT:-9090}:9090" command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=30d' volumes: - ./etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./etc/prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro - prometheus_data:/prometheus # ========================================================================== # Alertmanager - Alert routing and notifications (optional, use --profile metrics) # ========================================================================== alertmanager: image: prom/alertmanager:latest container_name: meshcore-alertmanager profiles: - all - metrics restart: unless-stopped ports: - "${ALERTMANAGER_PORT:-9093}:9093" volumes: - ./etc/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - alertmanager_data:/alertmanager command: - '--config.file=/etc/alertmanager/alertmanager.yml' - '--storage.path=/alertmanager' # ========================================================================== # Volumes # ========================================================================== volumes: hub_data: name: meshcore_hub_data mosquitto_data: name: meshcore_mosquitto_data mosquitto_log: name: meshcore_mosquitto_log prometheus_data: name: meshcore_prometheus_data alertmanager_data: name: meshcore_alertmanager_data