trails/infrastructure/docker-compose.yml
Ullrich Schäfer 7994ea79f4
Fix overview error rate panel, add request metrics to journal
Dashboard: switch error rate panel from app-level http_request_duration
(never observed) to Caddy's caddy_http_response_duration_seconds_count.

Journal: add custom server.ts (matching planner pattern) that observes
http_request_duration_seconds on every request. Replaces react-router-serve
with a custom Node HTTP server that handles /api/health, /api/metrics,
static assets, and request timing. Removes redundant React Router routes
for health and metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:20:18 +02:00

204 lines
6.1 KiB
YAML

services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
DOMAIN: ${DOMAIN:-trails.cool}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- journal
- planner
journal:
image: ghcr.io/trails-cool/journal:latest
restart: unless-stopped
environment:
DOMAIN: ${DOMAIN:-trails.cool}
ORIGIN: https://${DOMAIN:-trails.cool}
PLANNER_URL: https://planner.${DOMAIN:-trails.cool}
DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails
JWT_SECRET: ${JWT_SECRET:-change-me-in-production}
SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production}
NODE_ENV: production
PORT: 3000
SENTRY_RELEASE: ${SENTRY_RELEASE:-}
SMTP_URL: ${SMTP_URL:-}
SMTP_FROM: ${SMTP_FROM:-trails.cool <noreply@trails.cool>}
WAHOO_CLIENT_ID: ${WAHOO_CLIENT_ID:-}
WAHOO_CLIENT_SECRET: ${WAHOO_CLIENT_SECRET:-}
WAHOO_WEBHOOK_TOKEN: ${WAHOO_WEBHOOK_TOKEN:-}
healthcheck:
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3000/api/health').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))\""]
interval: 15s
timeout: 5s
retries: 3
depends_on:
postgres:
condition: service_healthy
planner:
image: ghcr.io/trails-cool/planner:latest
restart: unless-stopped
environment:
BROUTER_URL: http://brouter:17777
DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails
NODE_ENV: production
PORT: 3001
SENTRY_RELEASE: ${SENTRY_RELEASE:-}
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:3001/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
depends_on:
postgres:
condition: service_healthy
brouter:
condition: service_started
brouter:
image: ghcr.io/trails-cool/brouter:latest
restart: unless-stopped
volumes:
- ./segments:/data/segments
# Segments can be pulled from:
# - https://brouter.de/brouter/segments4/ (official, weekly updates)
# - A trails.cool CDN mirror (later)
postgres:
image: postgis/postgis:16-3.4
restart: unless-stopped
environment:
POSTGRES_USER: trails
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-trails}
POSTGRES_DB: trails
volumes:
- pgdata:/var/lib/postgresql/data
- ./postgres/init-grafana-user.sql:/docker-entrypoint-initdb.d/init-grafana-user.sql:ro
command:
- "postgres"
- "-c"
- "shared_preload_libraries=pg_stat_statements"
- "-c"
- "pg_stat_statements.track=all"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U trails"]
interval: 5s
timeout: 5s
retries: 5
postgres-exporter:
image: prometheuscommunity/postgres-exporter:latest
restart: unless-stopped
environment:
DATA_SOURCE_NAME: postgresql://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails?sslmode=disable
PG_EXPORTER_EXTEND_QUERY_PATH: /etc/postgres-exporter/queries.yml
volumes:
- ./postgres/queries.yml:/etc/postgres-exporter/queries.yml:ro
depends_on:
postgres:
condition: service_healthy
node-exporter:
image: prom/node-exporter:latest
restart: unless-stopped
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- "--path.procfs=/host/proc"
- "--path.sysfs=/host/sys"
- "--path.rootfs=/rootfs"
- "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
restart: unless-stopped
privileged: true
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
promtail:
image: grafana/promtail:latest
restart: unless-stopped
volumes:
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail/promtail-config.yml:/etc/promtail/config.yml:ro
command: ["-config.file=/etc/promtail/config.yml"]
depends_on:
- loki
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=15d"
- "--storage.tsdb.retention.size=1GB"
loki:
image: grafana/loki:latest
restart: unless-stopped
volumes:
- ./loki/loki-config.yml:/etc/loki/local-config.yaml:ro
- loki_data:/loki
command: ["-config.file=/etc/loki/local-config.yaml"]
grafana:
image: grafana/grafana:latest
restart: unless-stopped
environment:
GF_SERVER_ROOT_URL: https://grafana.internal.${DOMAIN:-trails.cool}
GF_AUTH_GITHUB_ENABLED: "true"
GF_AUTH_GITHUB_CLIENT_ID: ${GF_AUTH_GITHUB_CLIENT_ID:-}
GF_AUTH_GITHUB_CLIENT_SECRET: ${GF_AUTH_GITHUB_CLIENT_SECRET:-}
GF_AUTH_GITHUB_ALLOWED_ORGANIZATIONS: trails-cool
GF_AUTH_GITHUB_SCOPES: user:email,read:org
GF_AUTH_OAUTH_ALLOW_INSECURE_EMAIL_LOOKUP: "true"
GF_AUTH_DISABLE_LOGIN_FORM: "true"
GF_SMTP_ENABLED: "true"
GF_SMTP_HOST: ${GF_SMTP_HOST:-}
GF_SMTP_USER: ${GF_SMTP_USER:-}
GF_SMTP_PASSWORD: ${GF_SMTP_PASSWORD:-}
GF_SMTP_FROM_ADDRESS: noreply@trails.cool
GF_SMTP_FROM_NAME: trails.cool Grafana
GRAFANA_DB_PASSWORD: ${GRAFANA_DB_PASSWORD:-}
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
- loki
# garage:
# image: dxflrs/garage:v1.0
# restart: unless-stopped
# volumes:
# - garage_data:/var/lib/garage
# - ./garage.toml:/etc/garage.toml:ro
volumes:
caddy_data:
caddy_config:
pgdata:
prometheus_data:
loki_data:
grafana_data: