compose.yml544 lines · main
1# briven — canonical self-host compose (build-from-source).
2#
3# Post ADR-0002 (docs/ADR/0002-converge-on-doltgres.md): the platform
4# runs on TWO database engines, on purpose:
5#
6# - control plane = DoltGres database briven_control (product line: all Doltgres).
7# sign-in, orgs, projects, billing, secrets, auth. drizzle migrations
8# run at api boot. NEVER points at DoltGres.
9# - data plane = DoltGres (dolthub/doltgresql), env BRIVEN_DATA_PLANE_URL.
10# Postgres-wire, accessed with the `pg` driver, one DATABASE per
11# customer project. Realtime polls DOLT_HASHOF('HEAD') (no LISTEN/NOTIFY).
12#
13# This file supersedes the two earlier conflicting composes:
14# - the old Dolt-MySQL build (dolthub/dolt-sql-server + BRIVEN_URL mysql) —
15# wrong engine, broke the api. REMOVED.
16# - the all-pgvector build (data plane as a 2nd Postgres DB) — wrong data
17# plane. REPLACED by the real DoltGres service below.
18#
19# Dokploy clones the repo and runs `docker compose build` against the local
20# Dockerfiles — no external registry, no GHCR, no docker.sock. Per
21# docs/DOCKER.md §7 / infra/CLAUDE.md: every long-running service caps its
22# log volume via the *briven-logging anchor; no watchtower, no docker_sd,
23# no registry polling on the host.
24#
25# Single-machine layout, ~25 concurrent customer projects. Past that, split
26# the control plane onto one host and the data plane (doltgres + minio) onto
27# another.
28#
29# Required env (drop a `.env` next to this file — see .env.example):
30#
31# BRIVEN_DOMAIN e.g. briven.example.com
32# BRIVEN_BETTER_AUTH_SECRET openssl rand -hex 32
33# BRIVEN_AUDIT_IP_PEPPER openssl rand -hex 32
34# BRIVEN_ENCRYPTION_KEY openssl rand -hex 32
35# BRIVEN_RUNTIME_SHARED_SECRET openssl rand -hex 32
36# BRIVEN_POSTGRES_PASSWORD control-plane Postgres superuser password
37# BRIVEN_DOLTGRES_PASSWORD data-plane DoltGres superuser password
38# BRIVEN_MINIO_ROOT_PASSWORD MinIO root / S3 secret key
39# (optional) BRIVEN_MITTERA_*, BRIVEN_*_CLIENT_ID/SECRET, BRIVEN_POLAR_*,
40# BRIVEN_OLLAMA_*, BRIVEN_MINIO_BUCKET/REGION, BRIVEN_OPEN_SIGNUPS
41#
42# After first boot:
43# 1. Create the first user via the magic-link flow on https://${BRIVEN_DOMAIN}
44# 2. Promote to admin in the control plane:
45# docker exec -it briven-postgres psql -U postgres -d briven_control \
46# -c "UPDATE users SET is_admin = true WHERE id = '...';"
47# 3. Create your first project via the dashboard (provisions a DoltGres DB).
48
49# Explicit project name so compose resources are deterministically `briven_*`
50# (containers already pin container_name: briven-*). Without this, the project
51# name defaults to the parent directory ("dokploy"), which would create
52# `dokploy_*` volumes — confusingly close to the Dokploy platform's own naming
53# and a trap for host-maintenance tooling. Build AND up must share this name.
54name: briven
55
56x-logging: &briven-logging
57 driver: json-file
58 options:
59 max-size: '10m'
60 max-file: '3'
61
62services:
63 # ─── control plane ────────────────────────────────────────────────────
64 postgres:
65 image: pgvector/pgvector:pg17
66 container_name: briven-postgres
67 restart: unless-stopped
68 logging: *briven-logging
69 environment:
70 POSTGRES_PASSWORD: ${BRIVEN_POSTGRES_PASSWORD}
71 POSTGRES_DB: briven_control
72 volumes:
73 - postgres_data:/var/lib/postgresql/data
74 # Control-plane init only: enables pgvector + pg_trgm on briven_control.
75 # No data-plane DB is created here — the data plane is the doltgres
76 # service, with a database per project (see ADR-0002).
77 - ./postgres-init:/docker-entrypoint-initdb.d:ro
78 healthcheck:
79 test: ['CMD-SHELL', 'pg_isready -U postgres -d briven_control']
80 interval: 10s
81 timeout: 5s
82 retries: 5
83 start_period: 20s
84 networks:
85 - briven
86 labels:
87 - 'briven_logs=true'
88
89 # ─── data plane ───────────────────────────────────────────────────────
90 # DoltGres = Postgres-wire, git-for-data. Each customer project is its own
91 # DATABASE here, created by the api over the `pg` driver. The default
92 # superuser/database is `postgres`/`postgres` (DOLTGRES_* envs override the
93 # password).
94 #
95 # IMAGE IS PINNED BY DIGEST, ON PURPOSE (2026-07-07 maintenance window):
96 # `:latest` let a deploy silently swap the database engine under live data
97 # (prime suspect in the 2026-07-07 auth.db outage). To upgrade the engine,
98 # change the digest here deliberately, in its own reviewed deploy.
99 #
100 # DATA DIR IS /var/lib/doltgres — NO "ql". The mount below once pointed at
101 # /var/lib/doltgresql (typo), so all real data lived in an anonymous volume
102 # that a container recreation would orphan. Fixed 2026-07-07 (data migrated
103 # into the named volume during the maintenance window). Never change this
104 # path without checking `config.yaml` inside the volume.
105 doltgres:
106 image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
107 container_name: briven-doltgres
108 restart: unless-stopped
109 logging: *briven-logging
110 environment:
111 DOLTGRES_USER: postgres
112 DOLTGRES_PASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
113 volumes:
114 - doltgres_data:/var/lib/doltgres
115 # Dolt-native backups land here (written by the server itself); the
116 # dolt-backup service triggers them. See that service for details.
117 - doltgres_backups:/backups
118 healthcheck:
119 # pg_isready ships in the doltgresql image and needs no password.
120 test: ['CMD-SHELL', 'pg_isready -h 127.0.0.1 -p 5432 -U postgres']
121 interval: 10s
122 timeout: 5s
123 retries: 5
124 start_period: 30s
125 networks:
126 - briven
127 labels:
128 - 'briven_logs=true'
129
130 # ─── data-plane backup ──────────────────────────────────────────────────
131 # REAL DoltGres backup (replaces the old placeholder sleep loop).
132 #
133 # Why NOT pg_dump: tested 2026-06-26 against dolthub/doltgresql:latest
134 # (v0.56.6) — `pg_dump` aborts immediately with
135 # "ERROR: SET TRANSACTION is not yet supported"
136 # because pg_dump opens a REPEATABLE READ READ ONLY snapshot transaction
137 # that DoltGres does not implement. So pg_dump CANNOT back up the data
138 # plane. (The control plane is real Postgres and is dumped separately by
139 # the host timers in infra/backups/.)
140 #
141 # What works (verified same day): Dolt's own backup, invoked over the
142 # Postgres wire with `SELECT dolt_backup('sync-url', '<file-url>')`. It
143 # writes a full, version-history-preserving Dolt archive (manifest +
144 # .darc) — re-running it re-syncs in place, so one backup dir per database
145 # already contains every commit (time-travel restore, not just a snapshot).
146 #
147 # This sidecar reuses the doltgresql image (it has `psql`), enumerates the
148 # data-plane databases each run, and asks the doltgres SERVER to back each
149 # one up into the shared `doltgres_backups` volume.
150 #
151 # OFF-SITE follow-up: mirroring the `doltgres_backups` volume to MinIO/B2/R2
152 # is done today by the host systemd timers in infra/backups/ (mc-based, see
153 # briven-backup.sh). Folding an `mc mirror /backups -> minio` step into this
154 # service needs an image carrying both psql and mc; tracked as a follow-up.
155 dolt-backup:
156 # Pinned to the SAME digest as the doltgres service (2026-07-07 window) —
157 # the sidecar's psql must always match the server's engine version.
158 image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
159 container_name: briven-dolt-backup
160 restart: unless-stopped
161 logging: *briven-logging
162 depends_on:
163 doltgres:
164 condition: service_healthy
165 environment:
166 PGHOST: doltgres
167 PGPORT: '5432'
168 PGUSER: postgres
169 PGPASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
170 BRIVEN_BACKUP_INTERVAL_SECONDS: ${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}
171 volumes:
172 - doltgres_backups:/backups
173 # Read-only view of the server's data dir, ONLY so auth.db (the engine's
174 # users/grants file — corrupted once on 2026-07-07, nothing backed it up)
175 # can be snapshotted alongside the dolt backups below.
176 - doltgres_data:/doltgres-data:ro
177 entrypoint: ['/bin/sh', '-c']
178 command:
179 - |
180 set -eu
181 echo "dolt-backup: starting (interval=${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}s)"
182 while true; do
183 ts="$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
184 echo "[dolt-backup $$ts] enumerating data-plane databases"
185 # All non-template databases on the doltgres server (one per project,
186 # plus the default `postgres`). -tA = tuples only, unaligned.
187 dbs="$$(psql -tA -d postgres -c \
188 "SELECT datname FROM pg_database WHERE datname NOT IN ('template0','template1')")"
189 for db in $$dbs; do
190 echo "[dolt-backup $$ts] backing up $$db -> file:///backups/$$db"
191 if psql -d "$$db" -c \
192 "SELECT dolt_backup('sync-url', 'file:///backups/$$db');" >/dev/null; then
193 echo "[dolt-backup $$ts] ok $$db"
194 else
195 echo "[dolt-backup $$ts] WARN backup failed for $$db"
196 fi
197 done
198 # auth.db snapshot — tiny file, changes only on role/grant edits.
199 # Keep the newest 14 copies (2 weeks at the daily default interval).
200 if [ -f /doltgres-data/auth.db ]; then
201 mkdir -p /backups/auth-db
202 if cp /doltgres-data/auth.db "/backups/auth-db/auth.db.$$ts"; then
203 echo "[dolt-backup $$ts] ok auth.db snapshot"
204 ls -1t /backups/auth-db | tail -n +15 | while read -r old; do
205 rm -f "/backups/auth-db/$$old"
206 done
207 else
208 echo "[dolt-backup $$ts] WARN auth.db snapshot failed"
209 fi
210 else
211 echo "[dolt-backup $$ts] WARN auth.db not found in data dir"
212 fi
213 echo "[dolt-backup $$ts] run complete; sleeping"
214 sleep "$${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}"
215 done
216 networks:
217 - briven
218 labels:
219 - 'briven_logs=true'
220
221 # ─── shared infra ─────────────────────────────────────────────────────
222 redis:
223 image: redis:7.4-alpine
224 container_name: briven-redis
225 restart: unless-stopped
226 logging: *briven-logging
227 command: redis-server --appendonly yes
228 volumes:
229 - redis_data:/data
230 healthcheck:
231 test: ['CMD-SHELL', 'redis-cli ping | grep -q PONG']
232 interval: 10s
233 timeout: 5s
234 retries: 5
235 start_period: 10s
236 networks:
237 - briven
238 labels:
239 - 'briven_logs=true'
240
241 minio:
242 image: minio/minio:latest
243 container_name: briven-minio
244 restart: unless-stopped
245 logging: *briven-logging
246 command: server /data --console-address ':9001'
247 environment:
248 MINIO_ROOT_USER: briven
249 MINIO_ROOT_PASSWORD: ${BRIVEN_MINIO_ROOT_PASSWORD}
250 volumes:
251 - minio_data:/data
252 healthcheck:
253 test: ['CMD-SHELL', 'curl -fsS http://localhost:9000/minio/health/live || exit 1']
254 interval: 15s
255 timeout: 5s
256 retries: 5
257 start_period: 20s
258 networks:
259 - briven
260 - dokploy-network
261 labels:
262 - 'briven_logs=true'
263 - 'traefik.enable=true'
264 - 'traefik.docker.network=dokploy-network'
265 # Public S3 endpoint — browsers PUT/GET with sigv4-presigned URLs the
266 # api mints. The api also reaches MinIO internally at http://minio:9000.
267 - 'traefik.http.routers.briven-s3.rule=Host(`s3.${BRIVEN_DOMAIN}`)'
268 - 'traefik.http.routers.briven-s3.entrypoints=websecure'
269 - 'traefik.http.routers.briven-s3.tls.certresolver=letsencrypt'
270 - 'traefik.http.routers.briven-s3.service=briven-s3'
271 - 'traefik.http.services.briven-s3.loadbalancer.server.port=9000'
272
273 # One-shot bucket creator. `mc mb --ignore-existing` is idempotent, so this
274 # runs every deploy and no-ops after the first. restart: 'no' = one-shot, so
275 # per infra/CLAUDE.md it does NOT need the logging cap.
276 minio-init:
277 image: minio/mc:latest
278 container_name: briven-minio-init
279 depends_on:
280 minio:
281 condition: service_healthy
282 entrypoint: >
283 /bin/sh -c "
284 until /usr/bin/mc alias set minio http://minio:9000 briven ${BRIVEN_MINIO_ROOT_PASSWORD} >/dev/null 2>&1; do
285 echo 'waiting for minio...'; sleep 2;
286 done;
287 /usr/bin/mc mb --ignore-existing minio/${BRIVEN_MINIO_BUCKET:-briven};
288 echo 'minio bucket ready: ${BRIVEN_MINIO_BUCKET:-briven}';
289 "
290 restart: 'no'
291 networks:
292 - briven
293
294 # ─── application services ─────────────────────────────────────────────
295 api:
296 build:
297 context: ../..
298 dockerfile: apps/api/Dockerfile
299 container_name: briven-api
300 restart: unless-stopped
301 logging: *briven-logging
302 depends_on:
303 postgres:
304 condition: service_healthy
305 doltgres:
306 condition: service_healthy
307 redis:
308 condition: service_healthy
309 environment:
310 BRIVEN_ENV: production
311 BRIVEN_API_PORT: '3001'
312 BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
313 BRIVEN_WEB_ORIGIN: https://${BRIVEN_DOMAIN}
314 BRIVEN_TRUSTED_ORIGINS: https://${BRIVEN_DOMAIN},https://api.${BRIVEN_DOMAIN}
315 # Control plane — stock Postgres, postgres.js/drizzle.
316 BRIVEN_DATABASE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/briven_control?sslmode=disable
317 # Data plane — DoltGres, `pg` driver, database-per-project. The api
318 # connects to the default `postgres` database and CREATEs per-project
319 # databases on this server.
320 BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
321 BRIVEN_REDIS_URL: redis://redis:6379
322 BRIVEN_RUNTIME_URL: http://runtime:3003
323 # Without this the api falls back to localhost:3004 and can't reach
324 # realtime — surfaces as realtime_stats_failed "Unable to connect".
325 BRIVEN_REALTIME_URL: http://realtime:3004
326 BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
327 BRIVEN_BETTER_AUTH_SECRET: ${BRIVEN_BETTER_AUTH_SECRET}
328 BRIVEN_AUDIT_IP_PEPPER: ${BRIVEN_AUDIT_IP_PEPPER}
329 BRIVEN_ENCRYPTION_KEY: ${BRIVEN_ENCRYPTION_KEY}
330 BRIVEN_MITTERA_API_URL: ${BRIVEN_MITTERA_API_URL:-}
331 BRIVEN_MITTERA_API_KEY: ${BRIVEN_MITTERA_API_KEY:-}
332 BRIVEN_MITTERA_WEBHOOK_SECRET: ${BRIVEN_MITTERA_WEBHOOK_SECRET:-}
333 BRIVEN_GOOGLE_CLIENT_ID: ${BRIVEN_GOOGLE_CLIENT_ID:-}
334 BRIVEN_GOOGLE_CLIENT_SECRET: ${BRIVEN_GOOGLE_CLIENT_SECRET:-}
335 BRIVEN_GITHUB_CLIENT_ID: ${BRIVEN_GITHUB_CLIENT_ID:-}
336 BRIVEN_GITHUB_CLIENT_SECRET: ${BRIVEN_GITHUB_CLIENT_SECRET:-}
337 BRIVEN_KONNOS_CLIENT_ID: ${BRIVEN_KONNOS_CLIENT_ID:-}
338 BRIVEN_KONNOS_CLIENT_SECRET: ${BRIVEN_KONNOS_CLIENT_SECRET:-}
339 BRIVEN_KONNOS_ISSUER: ${BRIVEN_KONNOS_ISSUER:-https://code.konnos.org}
340 BRIVEN_DISCORD_CLIENT_ID: ${BRIVEN_DISCORD_CLIENT_ID:-}
341 BRIVEN_DISCORD_CLIENT_SECRET: ${BRIVEN_DISCORD_CLIENT_SECRET:-}
342 BRIVEN_POLAR_API_BASE: ${BRIVEN_POLAR_API_BASE:-https://api.polar.sh}
343 BRIVEN_POLAR_ACCESS_TOKEN: ${BRIVEN_POLAR_ACCESS_TOKEN:-}
344 BRIVEN_POLAR_WEBHOOK_SECRET: ${BRIVEN_POLAR_WEBHOOK_SECRET:-}
345 BRIVEN_POLAR_PRO_PRODUCT_ID: ${BRIVEN_POLAR_PRO_PRODUCT_ID:-}
346 BRIVEN_POLAR_TEAM_PRODUCT_ID: ${BRIVEN_POLAR_TEAM_PRODUCT_ID:-}
347 BRIVEN_DOMAIN: ${BRIVEN_DOMAIN}
348 BRIVEN_OPEN_SIGNUPS: ${BRIVEN_OPEN_SIGNUPS:-false}
349 BRIVEN_OLLAMA_URL: ${BRIVEN_OLLAMA_URL:-}
350 BRIVEN_OLLAMA_API_KEY: ${BRIVEN_OLLAMA_API_KEY:-}
351 BRIVEN_OLLAMA_MODEL: ${BRIVEN_OLLAMA_MODEL:-qwen2.5-coder:32b}
352 BRIVEN_MINIO_ENDPOINT: http://minio:9000
353 BRIVEN_MINIO_PUBLIC_ENDPOINT: https://s3.${BRIVEN_DOMAIN}
354 BRIVEN_MINIO_ACCESS_KEY: briven
355 BRIVEN_MINIO_SECRET_KEY: ${BRIVEN_MINIO_ROOT_PASSWORD}
356 BRIVEN_MINIO_BUCKET: ${BRIVEN_MINIO_BUCKET:-briven}
357 BRIVEN_MINIO_REGION: ${BRIVEN_MINIO_REGION:-us-east-1}
358 healthcheck:
359 # /info is documented to never 500 (apps/api/Dockerfile). bun ships in
360 # the api image and has a built-in fetch.
361 test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3001/info').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
362 interval: 15s
363 timeout: 5s
364 retries: 5
365 start_period: 40s
366 networks:
367 - briven
368 - dokploy-network
369 labels:
370 - 'briven_logs=true'
371 - 'traefik.enable=true'
372 - 'traefik.docker.network=dokploy-network'
373 - 'traefik.http.routers.briven-api.rule=Host(`api.${BRIVEN_DOMAIN}`)'
374 - 'traefik.http.routers.briven-api.entrypoints=websecure'
375 - 'traefik.http.routers.briven-api.tls.certresolver=letsencrypt'
376 - 'traefik.http.routers.briven-api.service=briven-api'
377 - 'traefik.http.services.briven-api.loadbalancer.server.port=3001'
378
379 runtime:
380 build:
381 context: ../..
382 dockerfile: apps/runtime/Dockerfile
383 container_name: briven-runtime
384 restart: unless-stopped
385 logging: *briven-logging
386 depends_on:
387 api:
388 condition: service_started
389 doltgres:
390 condition: service_healthy
391 environment:
392 BRIVEN_ENV: production
393 BRIVEN_RUNTIME_PORT: '3003'
394 BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
395 BRIVEN_RUNTIME_EXECUTOR: deno
396 BRIVEN_RUNTIME_BUNDLE_DIR: /var/lib/briven/bundles
397 BRIVEN_API_INTERNAL_URL: http://api:3001
398 # Data plane — DoltGres (the old BRIVEN_URL mysql:// was wrong, removed).
399 BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
400 volumes:
401 - runtime_bundles:/var/lib/briven/bundles
402 healthcheck:
403 # Any HTTP response = process is up (port serving).
404 test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3003/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
405 interval: 15s
406 timeout: 5s
407 retries: 5
408 start_period: 40s
409 networks:
410 - briven
411 labels:
412 - 'briven_logs=true'
413
414 realtime:
415 build:
416 context: ../..
417 dockerfile: apps/realtime/Dockerfile
418 container_name: briven-realtime
419 restart: unless-stopped
420 logging: *briven-logging
421 depends_on:
422 doltgres:
423 condition: service_healthy
424 environment:
425 BRIVEN_ENV: production
426 BRIVEN_REALTIME_PORT: '3004'
427 BRIVEN_API_INTERNAL_URL: http://api:3001
428 BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
429 # Data plane — DoltGres. Realtime polls DOLT_HASHOF('HEAD') per project
430 # (no LISTEN/NOTIFY on DoltGres). The old BRIVEN_URL mysql:// was wrong.
431 BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
432 BRIVEN_REALTIME_POLL_MS: '500'
433 healthcheck:
434 test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3004/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
435 interval: 15s
436 timeout: 5s
437 retries: 5
438 start_period: 40s
439 networks:
440 - briven
441 - dokploy-network
442 labels:
443 - 'briven_logs=true'
444 - 'traefik.enable=true'
445 - 'traefik.docker.network=dokploy-network'
446 - 'traefik.http.routers.briven-realtime.rule=Host(`realtime.${BRIVEN_DOMAIN}`)'
447 - 'traefik.http.routers.briven-realtime.entrypoints=websecure'
448 - 'traefik.http.routers.briven-realtime.tls.certresolver=letsencrypt'
449 - 'traefik.http.services.briven-realtime.loadbalancer.server.port=3004'
450
451 web:
452 build:
453 context: ../..
454 dockerfile: apps/web/Dockerfile
455 container_name: briven-web
456 restart: unless-stopped
457 logging: *briven-logging
458 depends_on:
459 api:
460 condition: service_started
461 environment:
462 BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
463 BRIVEN_WEB_ORIGIN: https://${BRIVEN_DOMAIN}
464 NEXT_PUBLIC_BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
465 NEXT_PUBLIC_BRIVEN_HAS_GOOGLE_OAUTH: ${BRIVEN_GOOGLE_CLIENT_ID:+true}
466 NEXT_PUBLIC_BRIVEN_HAS_GITHUB_OAUTH: ${BRIVEN_GITHUB_CLIENT_ID:+true}
467 NEXT_PUBLIC_BRIVEN_HAS_KONNOS_OAUTH: ${BRIVEN_KONNOS_CLIENT_ID:+true}
468 NEXT_PUBLIC_BRIVEN_HAS_DISCORD_OAUTH: ${BRIVEN_DISCORD_CLIENT_ID:+true}
469 healthcheck:
470 # web runs `next start` on node — use node's built-in fetch.
471 test: ['CMD-SHELL', "node -e \"fetch('http://localhost:3000/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
472 interval: 15s
473 timeout: 5s
474 retries: 5
475 start_period: 40s
476 networks:
477 - briven
478 - dokploy-network
479 labels:
480 - 'briven_logs=true'
481 - 'traefik.enable=true'
482 - 'traefik.docker.network=dokploy-network'
483 - 'traefik.http.routers.briven-web.rule=Host(`${BRIVEN_DOMAIN}`) || Host(`app.${BRIVEN_DOMAIN}`)'
484 - 'traefik.http.routers.briven-web.entrypoints=websecure'
485 - 'traefik.http.routers.briven-web.tls.certresolver=letsencrypt'
486 - 'traefik.http.services.briven-web.loadbalancer.server.port=3000'
487
488 docs:
489 build:
490 context: ../..
491 dockerfile: apps/docs/Dockerfile
492 container_name: briven-docs
493 restart: unless-stopped
494 logging: *briven-logging
495 depends_on:
496 api:
497 condition: service_started
498 environment:
499 # Used by /status + /api/status/incidents.xml to read live incidents.
500 BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
501 healthcheck:
502 test: ['CMD-SHELL', "node -e \"fetch('http://localhost:3002/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
503 interval: 15s
504 timeout: 5s
505 retries: 5
506 start_period: 40s
507 networks:
508 - briven
509 - dokploy-network
510 labels:
511 - 'briven_logs=true'
512 - 'traefik.enable=true'
513 - 'traefik.docker.network=dokploy-network'
514 - 'traefik.http.routers.briven-docs.rule=Host(`docs.${BRIVEN_DOMAIN}`)'
515 - 'traefik.http.routers.briven-docs.entrypoints=websecure'
516 - 'traefik.http.routers.briven-docs.tls.certresolver=letsencrypt'
517 - 'traefik.http.services.briven-docs.loadbalancer.server.port=3002'
518 # status.${BRIVEN_DOMAIN} — same docs container; bare `/` rewrites to
519 # /status. Other paths pass through (so /api/status/incidents.xml works).
520 - 'traefik.http.routers.briven-status.rule=Host(`status.${BRIVEN_DOMAIN}`)'
521 - 'traefik.http.routers.briven-status.entrypoints=websecure'
522 - 'traefik.http.routers.briven-status.tls.certresolver=letsencrypt'
523 - 'traefik.http.routers.briven-status.service=briven-docs'
524 - 'traefik.http.routers.briven-status.middlewares=briven-status-rewrite'
525 - 'traefik.http.middlewares.briven-status-rewrite.replacepathregex.regex=^/$$'
526 - 'traefik.http.middlewares.briven-status-rewrite.replacepathregex.replacement=/status'
527
528volumes:
529 postgres_data:
530 doltgres_data:
531 doltgres_backups:
532 redis_data:
533 minio_data:
534 runtime_bundles:
535
536networks:
537 briven:
538 driver: bridge
539 name: briven
540 # Dokploy's ingress network — Traefik watches this for routing + TLS.
541 # Routed services (api, realtime, web, docs, s3) attach to it in addition
542 # to the internal `briven` network; DBs/redis/runtime stay internal-only.
543 dokploy-network:
544 external: true