# 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
