mirror of
https://github.com/jorijn/meshcore-stats.git
synced 2026-03-28 17:42:55 +01:00
The container runs with read_only: true for security hardening, but fontconfig needs a writable cache directory. Added tmpfs mount at /var/cache/fontconfig to allow fontconfig to write its cache without compromising the read-only filesystem security. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
136 lines
3.7 KiB
YAML
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.5 # 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
|