# Observability stack — Grafana + Loki + Prometheus + Promtail. # # Single Dokploy compose project. Sits on the same docker network as # apps/api, apps/runtime, apps/realtime so Prometheus can scrape their # /metrics endpoints by container name. Grafana is exposed via Traefik # behind Cloudflare Access; the rest are internal-only. # # Per DOCKER.md (project root): # - Promtail uses file-based discovery against /var/lib/docker/containers # (read-only). NO docker.sock mount. NO docker_sd_configs. # - Prometheus uses static_configs against service DNS names. NO # docker_sd_configs. NO scraping of unix:///var/run/docker.sock. # - Every service has logging.options.max-size set (rule §7). # # See ./README.md for the operator runbook. x-logging: &briven-logging driver: json-file options: max-size: '10m' max-file: '3' services: prometheus: image: prom/prometheus:v3.0.1 restart: unless-stopped logging: *briven-logging command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--storage.tsdb.retention.time=30d' - '--web.enable-lifecycle' volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./prometheus/rules:/etc/prometheus/rules:ro - prometheus_data:/prometheus depends_on: - alertmanager networks: - dokploy-network alertmanager: image: prom/alertmanager:v0.27.0 restart: unless-stopped logging: *briven-logging command: - '--config.file=/etc/alertmanager/alertmanager.yml' - '--storage.path=/alertmanager' volumes: - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - alertmanager_data:/alertmanager networks: - dokploy-network # Translator sidecar — alertmanager webhook → discord webhook payload. # Two instances, one per channel, each holding a different webhook url. # The url is held inside the container's env so it never lands in the # repo. J sets BRIVEN_DISCORD_WEBHOOK_ALERTS + BRIVEN_DISCORD_WEBHOOK_DEPLOYS # in the dokploy project's environment per CLAUDE.md §4.1 (BRIVEN_ prefix # on every briven-owned env var) and docs/runbooks/discord-setup.md §4. alertmanager-discord-alerts: image: benjojo/alertmanager-discord:latest restart: unless-stopped logging: *briven-logging environment: DISCORD_WEBHOOK: ${BRIVEN_DISCORD_WEBHOOK_ALERTS} LISTEN_ADDRESS: ':9094' networks: - dokploy-network alertmanager-discord-deploys: image: benjojo/alertmanager-discord:latest restart: unless-stopped logging: *briven-logging environment: DISCORD_WEBHOOK: ${BRIVEN_DISCORD_WEBHOOK_DEPLOYS} LISTEN_ADDRESS: ':9094' networks: - dokploy-network loki: image: grafana/loki:3.3.2 restart: unless-stopped logging: *briven-logging command: -config.file=/etc/loki/config.yaml volumes: - ./loki/config.yaml:/etc/loki/config.yaml:ro - loki_data:/loki networks: - dokploy-network # Promtail — file-based docker log discovery per DOCKER.md §1. The # /var/lib/docker/containers bind-mount is read-only so Promtail can # tail the json-file driver's output without touching the daemon. # DO NOT bind-mount /var/run/docker.sock here (rule §6). DO NOT switch # to docker_sd_configs (rule §1). promtail: image: grafana/promtail:3.3.2 restart: unless-stopped logging: *briven-logging command: -config.file=/etc/promtail/config.yaml volumes: - ./promtail/config.yaml:/etc/promtail/config.yaml:ro - /var/lib/docker/containers:/var/lib/docker/containers:ro depends_on: - loki networks: - dokploy-network # Host metrics — CPU (incl. steal), memory, swap, load, disk/filesystem. # Unprivileged; read-only host mounts; NO docker.sock (DOCKER.md §6). node-exporter: image: prom/node-exporter:v1.8.2 restart: unless-stopped logging: *briven-logging command: - '--path.rootfs=/host' - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' pid: host volumes: - /:/host:ro,rslave networks: - dokploy-network # Per-container CPU/memory — catches a runaway build before it swap-kills # the host. Reads cgroupfs + /var/lib/docker (read-only) for container # names; NO docker.sock mount, NO /var/run (DOCKER.md §6). If container # metrics don't appear on some kernels, add `privileged: true`. cadvisor: image: gcr.io/cadvisor/cadvisor:v0.49.1 restart: unless-stopped logging: *briven-logging command: - '--docker_only=false' - '--housekeeping_interval=30s' - '--store_container_labels=false' devices: - /dev/kmsg volumes: - /:/rootfs:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro - /dev/disk/:/dev/disk:ro networks: - dokploy-network grafana: image: grafana/grafana:11.4.0 restart: unless-stopped logging: *briven-logging environment: # Local-auth mode — operator signs in with the bootstrap admin # password set via env. Promotable to cf-access proxy auth later # by overriding GF_AUTH_PROXY_ENABLED=true and the header vars. GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} GF_USERS_ALLOW_SIGN_UP: 'false' GF_SECURITY_ALLOW_EMBEDDING: 'false' GF_ANALYTICS_REPORTING_ENABLED: 'false' GF_ANALYTICS_CHECK_FOR_UPDATES: 'false' volumes: - ./grafana/grafana.ini:/etc/grafana/grafana.ini:ro - ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro - grafana_data:/var/lib/grafana depends_on: - prometheus - loki networks: - dokploy-network labels: - 'traefik.enable=true' - 'traefik.docker.network=dokploy-network' - 'traefik.http.routers.briven-grafana.rule=Host(`grafana.briven.tech`)' - 'traefik.http.routers.briven-grafana.entrypoints=websecure' - 'traefik.http.routers.briven-grafana.tls.certresolver=letsencrypt' - 'traefik.http.services.briven-grafana.loadbalancer.server.port=3000' volumes: prometheus_data: alertmanager_data: loki_data: grafana_data: networks: dokploy-network: external: true