Files
meshcore-stats/docker-compose.yml
2026-01-08 21:53:13 +01:00

136 lines
3.7 KiB
YAML

# 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.11 # 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.27-alpine
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