# MeshCore Stats - Docker Compose Configuration # # Production deployment using published container image. # For local development, use: docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build # # Prerequisites: # 1. Copy meshcore.conf.example to meshcore.conf and configure # 2. For serial transport: Create docker-compose.override.yml with your device (see README) # 3. Ensure your user has access to the serial device (dialout group) # 4. Create data directories with correct ownership: # mkdir -p ./data/state ./out && sudo chown -R 1000:1000 ./data ./out services: # ========================================================================== # MeshCore Stats - Data collection and rendering # ========================================================================== meshcore-stats: image: ghcr.io/jorijn/meshcore-stats:0.2.18 # x-release-please-version container_name: meshcore-stats restart: unless-stopped # Load configuration from meshcore.conf env_file: - meshcore.conf # NOTE: Serial device must be added via docker-compose.override.yml # See README.md for examples. TCP transport users don't need devices. volumes: # Persistent storage for SQLite database and circuit breaker state - ./data/state:/data/state # Generated static site (served by nginx) - ./out:/out # Run as meshmon user (UID 1000) user: "1000:1000" # Add dialout group for serial port access group_add: - dialout # Security hardening security_opt: - no-new-privileges:true cap_drop: - ALL read_only: true tmpfs: - /tmp:noexec,nosuid,size=64m - /var/cache/fontconfig:noexec,nosuid,size=4m # Resource limits deploy: resources: limits: cpus: "1.0" memory: 512M reservations: cpus: "0.1" memory: 128M # Logging limits to prevent disk exhaustion logging: driver: json-file options: max-size: "10m" max-file: "3" # Health check healthcheck: test: ["CMD", "python", "-c", "import sqlite3; sqlite3.connect('/data/state/metrics.db').execute('SELECT 1')"] interval: 5m timeout: 30s start_period: 60s retries: 3 # ========================================================================== # nginx - Static site server # ========================================================================== nginx: image: nginx:1.29-alpine@sha256:1d13701a5f9f3fb01aaa88cef2344d65b6b5bf6b7d9fa4cf0dca557a8d7702ba container_name: meshcore-stats-nginx restart: unless-stopped # Run as nginx user (UID 101 in Alpine nginx image) user: "101:101" ports: - "8080:8080" volumes: # Mount generated static site from meshcore-stats container - ./out:/usr/share/nginx/html:ro # Custom nginx configuration - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro # Security hardening security_opt: - no-new-privileges:true cap_drop: - ALL # NET_BIND_SERVICE not needed for port 8080 (unprivileged) read_only: true tmpfs: - /var/cache/nginx:noexec,nosuid,size=16m,uid=101,gid=101 - /var/run:noexec,nosuid,size=1m,uid=101,gid=101 # Resource limits deploy: resources: limits: cpus: "0.5" memory: 64M reservations: cpus: "0.05" memory: 16M # Logging limits logging: driver: json-file options: max-size: "10m" max-file: "3" # Health check healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 10s start_period: 5s retries: 3 depends_on: meshcore-stats: condition: service_healthy