compose.yml189 lines · main
1# Observability stack — Grafana + Loki + Prometheus + Promtail.
2#
3# Single Dokploy compose project. Sits on the same docker network as
4# apps/api, apps/runtime, apps/realtime so Prometheus can scrape their
5# /metrics endpoints by container name. Grafana is exposed via Traefik
6# behind Cloudflare Access; the rest are internal-only.
7#
8# Per DOCKER.md (project root):
9# - Promtail uses file-based discovery against /var/lib/docker/containers
10# (read-only). NO docker.sock mount. NO docker_sd_configs.
11# - Prometheus uses static_configs against service DNS names. NO
12# docker_sd_configs. NO scraping of unix:///var/run/docker.sock.
13# - Every service has logging.options.max-size set (rule §7).
14#
15# See ./README.md for the operator runbook.
16
17x-logging: &briven-logging
18 driver: json-file
19 options:
20 max-size: '10m'
21 max-file: '3'
22
23services:
24 prometheus:
25 image: prom/prometheus:v3.0.1
26 restart: unless-stopped
27 logging: *briven-logging
28 command:
29 - '--config.file=/etc/prometheus/prometheus.yml'
30 - '--storage.tsdb.path=/prometheus'
31 - '--storage.tsdb.retention.time=30d'
32 - '--web.enable-lifecycle'
33 volumes:
34 - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
35 - ./prometheus/rules:/etc/prometheus/rules:ro
36 - prometheus_data:/prometheus
37 depends_on:
38 - alertmanager
39 networks:
40 - dokploy-network
41
42 alertmanager:
43 image: prom/alertmanager:v0.27.0
44 restart: unless-stopped
45 logging: *briven-logging
46 command:
47 - '--config.file=/etc/alertmanager/alertmanager.yml'
48 - '--storage.path=/alertmanager'
49 volumes:
50 - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
51 - alertmanager_data:/alertmanager
52 networks:
53 - dokploy-network
54
55 # Translator sidecar — alertmanager webhook → discord webhook payload.
56 # Two instances, one per channel, each holding a different webhook url.
57 # The url is held inside the container's env so it never lands in the
58 # repo. J sets BRIVEN_DISCORD_WEBHOOK_ALERTS + BRIVEN_DISCORD_WEBHOOK_DEPLOYS
59 # in the dokploy project's environment per CLAUDE.md §4.1 (BRIVEN_ prefix
60 # on every briven-owned env var) and docs/runbooks/discord-setup.md §4.
61 alertmanager-discord-alerts:
62 image: benjojo/alertmanager-discord:latest
63 restart: unless-stopped
64 logging: *briven-logging
65 environment:
66 DISCORD_WEBHOOK: ${BRIVEN_DISCORD_WEBHOOK_ALERTS}
67 LISTEN_ADDRESS: ':9094'
68 networks:
69 - dokploy-network
70
71 alertmanager-discord-deploys:
72 image: benjojo/alertmanager-discord:latest
73 restart: unless-stopped
74 logging: *briven-logging
75 environment:
76 DISCORD_WEBHOOK: ${BRIVEN_DISCORD_WEBHOOK_DEPLOYS}
77 LISTEN_ADDRESS: ':9094'
78 networks:
79 - dokploy-network
80
81 loki:
82 image: grafana/loki:3.3.2
83 restart: unless-stopped
84 logging: *briven-logging
85 command: -config.file=/etc/loki/config.yaml
86 volumes:
87 - ./loki/config.yaml:/etc/loki/config.yaml:ro
88 - loki_data:/loki
89 networks:
90 - dokploy-network
91
92 # Promtail — file-based docker log discovery per DOCKER.md §1. The
93 # /var/lib/docker/containers bind-mount is read-only so Promtail can
94 # tail the json-file driver's output without touching the daemon.
95 # DO NOT bind-mount /var/run/docker.sock here (rule §6). DO NOT switch
96 # to docker_sd_configs (rule §1).
97 promtail:
98 image: grafana/promtail:3.3.2
99 restart: unless-stopped
100 logging: *briven-logging
101 command: -config.file=/etc/promtail/config.yaml
102 volumes:
103 - ./promtail/config.yaml:/etc/promtail/config.yaml:ro
104 - /var/lib/docker/containers:/var/lib/docker/containers:ro
105 depends_on:
106 - loki
107 networks:
108 - dokploy-network
109
110 # Host metrics — CPU (incl. steal), memory, swap, load, disk/filesystem.
111 # Unprivileged; read-only host mounts; NO docker.sock (DOCKER.md §6).
112 node-exporter:
113 image: prom/node-exporter:v1.8.2
114 restart: unless-stopped
115 logging: *briven-logging
116 command:
117 - '--path.rootfs=/host'
118 - '--path.procfs=/host/proc'
119 - '--path.sysfs=/host/sys'
120 - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
121 pid: host
122 volumes:
123 - /:/host:ro,rslave
124 networks:
125 - dokploy-network
126
127 # Per-container CPU/memory — catches a runaway build before it swap-kills
128 # the host. Reads cgroupfs + /var/lib/docker (read-only) for container
129 # names; NO docker.sock mount, NO /var/run (DOCKER.md §6). If container
130 # metrics don't appear on some kernels, add `privileged: true`.
131 cadvisor:
132 image: gcr.io/cadvisor/cadvisor:v0.49.1
133 restart: unless-stopped
134 logging: *briven-logging
135 command:
136 - '--docker_only=false'
137 - '--housekeeping_interval=30s'
138 - '--store_container_labels=false'
139 devices:
140 - /dev/kmsg
141 volumes:
142 - /:/rootfs:ro
143 - /sys:/sys:ro
144 - /var/lib/docker/:/var/lib/docker:ro
145 - /dev/disk/:/dev/disk:ro
146 networks:
147 - dokploy-network
148
149 grafana:
150 image: grafana/grafana:11.4.0
151 restart: unless-stopped
152 logging: *briven-logging
153 environment:
154 # Local-auth mode — operator signs in with the bootstrap admin
155 # password set via env. Promotable to cf-access proxy auth later
156 # by overriding GF_AUTH_PROXY_ENABLED=true and the header vars.
157 GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
158 GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
159 GF_USERS_ALLOW_SIGN_UP: 'false'
160 GF_SECURITY_ALLOW_EMBEDDING: 'false'
161 GF_ANALYTICS_REPORTING_ENABLED: 'false'
162 GF_ANALYTICS_CHECK_FOR_UPDATES: 'false'
163 volumes:
164 - ./grafana/grafana.ini:/etc/grafana/grafana.ini:ro
165 - ./grafana/provisioning:/etc/grafana/provisioning:ro
166 - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
167 - grafana_data:/var/lib/grafana
168 depends_on:
169 - prometheus
170 - loki
171 networks:
172 - dokploy-network
173 labels:
174 - 'traefik.enable=true'
175 - 'traefik.docker.network=dokploy-network'
176 - 'traefik.http.routers.briven-grafana.rule=Host(`grafana.briven.tech`)'
177 - 'traefik.http.routers.briven-grafana.entrypoints=websecure'
178 - 'traefik.http.routers.briven-grafana.tls.certresolver=letsencrypt'
179 - 'traefik.http.services.briven-grafana.loadbalancer.server.port=3000'
180
181volumes:
182 prometheus_data:
183 alertmanager_data:
184 loki_data:
185 grafana_data:
186
187networks:
188 dokploy-network:
189 external: true