Files
meshcore-stats/docker-compose.yml
Jorijn Schrijvershof ee959d95a1 fix: improve Docker configuration and documentation
- Change Python path defaults to Docker paths (/data/state, /out)
- Remove STATE_DIR/OUT_DIR from Dockerfile ENV (Python defaults now correct)
- Remove REPEATER_FETCH_ACL feature (unsupported)
- Fix nginx tmpfs permissions with uid=101,gid=101
- Remove Ofelia [global] save=true (caused config parse error)
- Switch to bind mounts for ./out instead of named volume
- Comment out devices section (not available on macOS Docker)
- Add TCP and BLE transport options to meshcore.conf.example
- Document correct macOS socat command for serial-over-TCP
- Update README with macOS Docker workaround instructions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 07:56:58 +01:00

136 lines
3.6 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.development.yml up
#
# Prerequisites:
# 1. Copy meshcore.conf.example to meshcore.conf and configure
# 2. Ensure your user has access to the serial device (dialout group)
# 3. 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.3.0 # x-release-please-version
container_name: meshcore-stats
restart: unless-stopped
# Load configuration from meshcore.conf
env_file:
- meshcore.conf
# Serial device for companion node communication
devices:
# Update to match your serial device (e.g., /dev/ttyACM0, /dev/cu.usbserial-*)
- /dev/ttyUSB0:/dev/ttyUSB0:rw
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
# 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