compose.dokploy.yml 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. # briven — Dokploy-managed compose (generated from compose.yml).
  2. # Dokploy controls the project name + container names, so this file omits
  3. # the top-level `name:` and per-service `container_name:` that compose.yml
  4. # pins for the raw self-host path. Keep this in sync with compose.yml.
  5. # Source of truth for the engine/services is compose.yml (DoltGres data plane).
  6. # briven — canonical self-host compose (build-from-source).
  7. #
  8. # Product line (flndrn 2026-07-21): Briven is Doltgres end-to-end.
  9. #
  10. # - control plane = DoltGres database `briven_control` on the doltgres service
  11. # (sign-in, orgs, projects, billing, Briven Auth). BRIVEN_DATABASE_URL.
  12. # - data plane = same DoltGres cluster, one DATABASE per customer project
  13. # (`proj_…`). BRIVEN_DATA_PLANE_URL. Realtime polls DOLT_HASHOF('HEAD').
  14. #
  15. # Stock Postgres (pgvector) may still exist on the host for rollback only —
  16. # live traffic must not use it. Do not reintroduce control-on-Postgres.
  17. #
  18. # This file supersedes the two earlier conflicting composes:
  19. # - the old Dolt-MySQL build (dolthub/dolt-sql-server + BRIVEN_URL mysql) —
  20. # wrong engine, broke the api. REMOVED.
  21. # - the all-pgvector build (data plane as a 2nd Postgres DB) — wrong data
  22. # plane. REPLACED by the real DoltGres service below.
  23. #
  24. # Dokploy clones the repo and runs `docker compose build` against the local
  25. # Dockerfiles — no external registry, no GHCR, no docker.sock. Per
  26. # docs/DOCKER.md §7 / infra/CLAUDE.md: every long-running service caps its
  27. # log volume via the *briven-logging anchor; no watchtower, no docker_sd,
  28. # no registry polling on the host.
  29. #
  30. # Single-machine layout, ~25 concurrent customer projects. Past that, split
  31. # the control plane onto one host and the data plane (doltgres + minio) onto
  32. # another.
  33. #
  34. # Required env (drop a `.env` next to this file — see .env.example):
  35. #
  36. # BRIVEN_DOMAIN e.g. briven.example.com
  37. # BRIVEN_BETTER_AUTH_SECRET openssl rand -hex 32
  38. # BRIVEN_AUDIT_IP_PEPPER openssl rand -hex 32
  39. # BRIVEN_ENCRYPTION_KEY openssl rand -hex 32
  40. # BRIVEN_RUNTIME_SHARED_SECRET openssl rand -hex 32
  41. # BRIVEN_POSTGRES_PASSWORD control-plane Postgres superuser password
  42. # BRIVEN_DOLTGRES_PASSWORD data-plane DoltGres superuser password
  43. # BRIVEN_MINIO_ROOT_PASSWORD MinIO root / S3 secret key
  44. # (optional) BRIVEN_MITTERA_*, BRIVEN_*_CLIENT_ID/SECRET, BRIVEN_POLAR_*,
  45. # BRIVEN_OLLAMA_*, BRIVEN_MINIO_BUCKET/REGION, BRIVEN_OPEN_SIGNUPS
  46. #
  47. # After first boot:
  48. # 1. Create the first user via the magic-link flow on https://${BRIVEN_DOMAIN}
  49. # 2. Promote to admin in the control plane:
  50. # docker exec -it briven-postgres psql -U postgres -d briven_control \
  51. # -c "UPDATE users SET is_admin = true WHERE id = '...';"
  52. # 3. Create your first project via the dashboard (provisions a DoltGres DB).
  53. x-logging: &briven-logging
  54. driver: json-file
  55. options:
  56. max-size: '10m'
  57. max-file: '3'
  58. services:
  59. # ─── control plane ────────────────────────────────────────────────────
  60. postgres:
  61. image: pgvector/pgvector:pg17
  62. restart: unless-stopped
  63. logging: *briven-logging
  64. environment:
  65. POSTGRES_PASSWORD: ${BRIVEN_POSTGRES_PASSWORD}
  66. POSTGRES_DB: briven_control
  67. volumes:
  68. - postgres_data:/var/lib/postgresql/data
  69. # Control-plane init only: enables pgvector + pg_trgm on briven_control.
  70. # No data-plane DB is created here — the data plane is the doltgres
  71. # service, with a database per project (see ADR-0002).
  72. - ./postgres-init:/docker-entrypoint-initdb.d:ro
  73. healthcheck:
  74. test: ['CMD-SHELL', 'pg_isready -U postgres -d briven_control']
  75. interval: 10s
  76. timeout: 5s
  77. retries: 5
  78. start_period: 20s
  79. networks:
  80. - briven
  81. labels:
  82. - 'briven_logs=true'
  83. # ─── data plane ───────────────────────────────────────────────────────
  84. # DoltGres = Postgres-wire, git-for-data. Each customer project is its own
  85. # DATABASE here, created by the api over the `pg` driver. The default
  86. # superuser/database is `postgres`/`postgres` (DOLTGRES_* envs override the
  87. # password).
  88. #
  89. # IMAGE IS PINNED BY DIGEST, ON PURPOSE (2026-07-07 maintenance window):
  90. # `:latest` let a deploy silently swap the database engine under live data
  91. # (prime suspect in the 2026-07-07 auth.db outage). To upgrade the engine,
  92. # change the digest here deliberately, in its own reviewed deploy.
  93. #
  94. # DATA DIR IS /var/lib/doltgres — NO "ql". The mount below once pointed at
  95. # /var/lib/doltgresql (typo), so all real data lived in an anonymous volume
  96. # that a container recreation would orphan. Fixed 2026-07-07 (data migrated
  97. # into the named volume during the maintenance window). Never change this
  98. # path without checking `config.yaml` inside the volume.
  99. doltgres:
  100. image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
  101. restart: unless-stopped
  102. logging: *briven-logging
  103. environment:
  104. DOLTGRES_USER: postgres
  105. DOLTGRES_PASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
  106. volumes:
  107. - doltgres_data:/var/lib/doltgres
  108. # Dolt-native backups land here (written by the server itself); the
  109. # dolt-backup service triggers them. See that service for details.
  110. - doltgres_backups:/backups
  111. healthcheck:
  112. # pg_isready ships in the doltgresql image and needs no password.
  113. test: ['CMD-SHELL', 'pg_isready -h 127.0.0.1 -p 5432 -U postgres']
  114. interval: 10s
  115. timeout: 5s
  116. retries: 5
  117. start_period: 30s
  118. networks:
  119. - briven
  120. labels:
  121. - 'briven_logs=true'
  122. # ─── data-plane backup ──────────────────────────────────────────────────
  123. # REAL DoltGres backup (replaces the old placeholder sleep loop).
  124. #
  125. # Why NOT pg_dump: tested 2026-06-26 against dolthub/doltgresql:latest
  126. # (v0.56.6) — `pg_dump` aborts immediately with
  127. # "ERROR: SET TRANSACTION is not yet supported"
  128. # because pg_dump opens a REPEATABLE READ READ ONLY snapshot transaction
  129. # that DoltGres does not implement. So pg_dump CANNOT back up the data
  130. # plane. (The control plane is real Postgres and is dumped separately by
  131. # the host timers in infra/backups/.)
  132. #
  133. # What works (verified same day): Dolt's own backup, invoked over the
  134. # Postgres wire with `SELECT dolt_backup('sync-url', '<file-url>')`. It
  135. # writes a full, version-history-preserving Dolt archive (manifest +
  136. # .darc) — re-running it re-syncs in place, so one backup dir per database
  137. # already contains every commit (time-travel restore, not just a snapshot).
  138. #
  139. # This sidecar reuses the doltgresql image (it has `psql`), enumerates the
  140. # data-plane databases each run, and asks the doltgres SERVER to back each
  141. # one up into the shared `doltgres_backups` volume.
  142. #
  143. # OFF-SITE follow-up: mirroring the `doltgres_backups` volume to MinIO/B2/R2
  144. # is done today by the host systemd timers in infra/backups/ (mc-based, see
  145. # briven-backup.sh). Folding an `mc mirror /backups -> minio` step into this
  146. # service needs an image carrying both psql and mc; tracked as a follow-up.
  147. dolt-backup:
  148. # Pinned to the SAME digest as the doltgres service (2026-07-07 window) —
  149. # the sidecar's psql must always match the server's engine version.
  150. image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
  151. restart: unless-stopped
  152. logging: *briven-logging
  153. depends_on:
  154. doltgres:
  155. condition: service_healthy
  156. environment:
  157. PGHOST: doltgres
  158. PGPORT: '5432'
  159. PGUSER: postgres
  160. PGPASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
  161. BRIVEN_BACKUP_INTERVAL_SECONDS: ${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}
  162. volumes:
  163. - doltgres_backups:/backups
  164. # Read-only view of the server's data dir, ONLY so auth.db (the engine's
  165. # users/grants file — corrupted once on 2026-07-07, nothing backed it up)
  166. # can be snapshotted alongside the dolt backups below.
  167. - doltgres_data:/doltgres-data:ro
  168. entrypoint: ['/bin/sh', '-c']
  169. command:
  170. - |
  171. set -eu
  172. echo "dolt-backup: starting (interval=${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}s)"
  173. while true; do
  174. ts="$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  175. echo "[dolt-backup $$ts] enumerating data-plane databases"
  176. # All non-template databases on the doltgres server (one per project,
  177. # plus the default `postgres`). -tA = tuples only, unaligned.
  178. dbs="$$(psql -tA -d postgres -c \
  179. "SELECT datname FROM pg_database WHERE datname NOT IN ('template0','template1')")"
  180. for db in $$dbs; do
  181. echo "[dolt-backup $$ts] backing up $$db -> file:///backups/$$db"
  182. if psql -d "$$db" -c \
  183. "SELECT dolt_backup('sync-url', 'file:///backups/$$db');" >/dev/null; then
  184. echo "[dolt-backup $$ts] ok $$db"
  185. else
  186. echo "[dolt-backup $$ts] WARN backup failed for $$db"
  187. fi
  188. done
  189. # auth.db snapshot — tiny file, changes only on role/grant edits.
  190. # Keep the newest 14 copies (2 weeks at the daily default interval).
  191. if [ -f /doltgres-data/auth.db ]; then
  192. mkdir -p /backups/auth-db
  193. if cp /doltgres-data/auth.db "/backups/auth-db/auth.db.$$ts"; then
  194. echo "[dolt-backup $$ts] ok auth.db snapshot"
  195. ls -1t /backups/auth-db | tail -n +15 | while read -r old; do
  196. rm -f "/backups/auth-db/$$old"
  197. done
  198. else
  199. echo "[dolt-backup $$ts] WARN auth.db snapshot failed"
  200. fi
  201. else
  202. echo "[dolt-backup $$ts] WARN auth.db not found in data dir"
  203. fi
  204. echo "[dolt-backup $$ts] run complete; sleeping"
  205. sleep "$${BRIVEN_BACKUP_INTERVAL_SECONDS:-86400}"
  206. done
  207. networks:
  208. - briven
  209. labels:
  210. - 'briven_logs=true'
  211. # ─── briven-engine Auth DB (DOLTGRES ONLY — non-negotiable) ───────────
  212. # HARD RULE: the COMPLETE Briven project is Doltgres. New parts do not get
  213. # a separate stock-Postgres brain. SuperTokens Core Docker is REMOVED —
  214. # it is incompatible with Doltgres (SET SESSION CHARACTERISTICS).
  215. # briven-engine = Briven API code + tables in Doltgres DB `briven_engine`.
  216. # Create/migrate: API `ensureBrivenEngineDatabase` + schema bootstrap.
  217. briven-engine-db-init:
  218. image: dolthub/doltgresql@sha256:0483137d0309598d3b0c111dff85d565077bd91cb0524ce00bb832929d5d5ddc
  219. restart: 'no'
  220. logging: *briven-logging
  221. depends_on:
  222. doltgres:
  223. condition: service_healthy
  224. environment:
  225. PGPASSWORD: ${BRIVEN_DOLTGRES_PASSWORD}
  226. entrypoint:
  227. - /bin/sh
  228. - -c
  229. - |
  230. set -e
  231. echo "briven-engine-db-init: ensuring briven_engine on DOLTGRES only…"
  232. exists=$$(psql -h doltgres -U postgres -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='briven_engine'" || true)
  233. if [ "$$exists" = "1" ]; then
  234. echo "briven_engine already exists on doltgres"
  235. else
  236. psql -h doltgres -U postgres -d postgres -c 'CREATE DATABASE briven_engine'
  237. echo "created briven_engine on doltgres"
  238. fi
  239. networks:
  240. - briven
  241. labels:
  242. - 'briven.service=briven-engine-db-init'
  243. - 'briven.db=doltgres'
  244. # ─── shared infra ─────────────────────────────────────────────────────
  245. redis:
  246. image: redis:7.4-alpine
  247. restart: unless-stopped
  248. logging: *briven-logging
  249. command: redis-server --appendonly yes
  250. volumes:
  251. - redis_data:/data
  252. healthcheck:
  253. test: ['CMD-SHELL', 'redis-cli ping | grep -q PONG']
  254. interval: 10s
  255. timeout: 5s
  256. retries: 5
  257. start_period: 10s
  258. networks:
  259. - briven
  260. labels:
  261. - 'briven_logs=true'
  262. minio:
  263. image: minio/minio:latest
  264. restart: unless-stopped
  265. logging: *briven-logging
  266. command: server /data --console-address ':9001'
  267. environment:
  268. MINIO_ROOT_USER: briven
  269. MINIO_ROOT_PASSWORD: ${BRIVEN_MINIO_ROOT_PASSWORD}
  270. volumes:
  271. - minio_data:/data
  272. healthcheck:
  273. test: ['CMD-SHELL', 'curl -fsS http://localhost:9000/minio/health/live || exit 1']
  274. interval: 15s
  275. timeout: 5s
  276. retries: 5
  277. start_period: 20s
  278. networks:
  279. - briven
  280. - dokploy-network
  281. labels:
  282. - 'briven_logs=true'
  283. - 'traefik.enable=true'
  284. - 'traefik.docker.network=dokploy-network'
  285. # Public S3 endpoint — browsers PUT/GET with sigv4-presigned URLs the
  286. # api mints. The api also reaches MinIO internally at http://minio:9000.
  287. - 'traefik.http.routers.briven-s3.rule=Host(`s3.${BRIVEN_DOMAIN}`)'
  288. - 'traefik.http.routers.briven-s3.entrypoints=websecure'
  289. - 'traefik.http.routers.briven-s3.tls.certresolver=letsencrypt'
  290. - 'traefik.http.routers.briven-s3.service=briven-s3'
  291. - 'traefik.http.services.briven-s3.loadbalancer.server.port=9000'
  292. # One-shot bucket creator. `mc mb --ignore-existing` is idempotent, so this
  293. # runs every deploy and no-ops after the first. restart: 'no' = one-shot, so
  294. # per infra/CLAUDE.md it does NOT need the logging cap.
  295. minio-init:
  296. image: minio/mc:latest
  297. depends_on:
  298. minio:
  299. condition: service_healthy
  300. entrypoint: >
  301. /bin/sh -c "
  302. until /usr/bin/mc alias set minio http://minio:9000 briven ${BRIVEN_MINIO_ROOT_PASSWORD} >/dev/null 2>&1; do
  303. echo 'waiting for minio...'; sleep 2;
  304. done;
  305. /usr/bin/mc mb --ignore-existing minio/${BRIVEN_MINIO_BUCKET:-briven};
  306. echo 'minio bucket ready: ${BRIVEN_MINIO_BUCKET:-briven}';
  307. "
  308. restart: 'no'
  309. networks:
  310. - briven
  311. # ─── application services ─────────────────────────────────────────────
  312. api:
  313. build:
  314. context: ../..
  315. dockerfile: apps/api/Dockerfile
  316. restart: unless-stopped
  317. logging: *briven-logging
  318. depends_on:
  319. postgres:
  320. condition: service_healthy
  321. doltgres:
  322. condition: service_healthy
  323. redis:
  324. condition: service_healthy
  325. briven-engine-db-init:
  326. condition: service_completed_successfully
  327. environment:
  328. BRIVEN_ENV: production
  329. BRIVEN_API_PORT: '3001'
  330. BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
  331. BRIVEN_WEB_ORIGIN: https://${BRIVEN_DOMAIN}
  332. BRIVEN_ADMIN_ORIGIN: https://admin.${BRIVEN_DOMAIN}
  333. # briven-engine = API + Doltgres only (NO SuperTokens Core container).
  334. BRIVEN_AUTH_CORE_ENABLED: ${BRIVEN_AUTH_CORE_ENABLED:-true}
  335. # SQL for Auth vault — ALWAYS Doltgres DB briven_engine (DOLTGRES-FIRST).
  336. BRIVEN_ENGINE_DATABASE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/briven_engine?sslmode=disable
  337. BRIVEN_AUTH_ENABLED: ${BRIVEN_AUTH_ENABLED:-false}
  338. # Observability stack (host-managed compose at /root/briven-observability
  339. # on the France box, same dokploy-network) — powers admin host gauges,
  340. # the live cpu chart, and /v1/admin/timeseries prom-backed series.
  341. BRIVEN_PROMETHEUS_URL: ${BRIVEN_PROMETHEUS_URL:-}
  342. # Hard allowlist: only these emails can EVER be platform admin
  343. # (see apps/api/src/lib/superadmin.ts). Value lives in the Dokploy env.
  344. BRIVEN_SUPERADMIN_EMAILS: ${BRIVEN_SUPERADMIN_EMAILS:-}
  345. BRIVEN_TRUSTED_ORIGINS: https://${BRIVEN_DOMAIN},https://app.${BRIVEN_DOMAIN},https://api.${BRIVEN_DOMAIN},https://admin.${BRIVEN_DOMAIN}
  346. # Control plane — stock Postgres, postgres.js/drizzle.
  347. # Control brain on Doltgres (same cluster as project DBs). Password is
  348. # DOLTGRES, not the legacy stock-Postgres password.
  349. BRIVEN_DATABASE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/briven_control?sslmode=disable
  350. # Data plane — DoltGres, `pg` driver, database-per-project. The api
  351. # connects to the default `postgres` database and CREATEs per-project
  352. # databases on this server.
  353. BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
  354. BRIVEN_REDIS_URL: redis://redis:6379
  355. BRIVEN_RUNTIME_URL: http://runtime:3003
  356. # Without this the api falls back to localhost:3004 and can't reach
  357. # realtime — surfaces as realtime_stats_failed "Unable to connect".
  358. BRIVEN_REALTIME_URL: http://realtime:3004
  359. BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
  360. BRIVEN_BETTER_AUTH_SECRET: ${BRIVEN_BETTER_AUTH_SECRET}
  361. BRIVEN_AUDIT_IP_PEPPER: ${BRIVEN_AUDIT_IP_PEPPER}
  362. BRIVEN_ENCRYPTION_KEY: ${BRIVEN_ENCRYPTION_KEY}
  363. # Per-tenant secret-store master key (ARCHITECTURE.md §4/§9).
  364. # BRIVEN_AUTH_ENABLED is set once above with the engine env block.
  365. # Values live in the Dokploy env panel; empty = unset in loadEnv.
  366. BRIVEN_AUTH_MASTER_KEY: ${BRIVEN_AUTH_MASTER_KEY:-}
  367. BRIVEN_MITTERA_API_URL: ${BRIVEN_MITTERA_API_URL:-}
  368. BRIVEN_MITTERA_API_KEY: ${BRIVEN_MITTERA_API_KEY:-}
  369. BRIVEN_MITTERA_WEBHOOK_SECRET: ${BRIVEN_MITTERA_WEBHOOK_SECRET:-}
  370. # Auth OTP / magic-link real inbox: set HOST+USER+PASS(+FROM) for SMTP primary.
  371. # Until SMTP is set, Auth uses mittera (same as platform mail).
  372. BRIVEN_SMTP_HOST: ${BRIVEN_SMTP_HOST:-}
  373. BRIVEN_SMTP_PORT: ${BRIVEN_SMTP_PORT:-587}
  374. BRIVEN_SMTP_USER: ${BRIVEN_SMTP_USER:-}
  375. BRIVEN_SMTP_PASS: ${BRIVEN_SMTP_PASS:-}
  376. BRIVEN_SMTP_FROM: ${BRIVEN_SMTP_FROM:-}
  377. BRIVEN_GOOGLE_CLIENT_ID: ${BRIVEN_GOOGLE_CLIENT_ID:-}
  378. BRIVEN_GOOGLE_CLIENT_SECRET: ${BRIVEN_GOOGLE_CLIENT_SECRET:-}
  379. BRIVEN_GITHUB_CLIENT_ID: ${BRIVEN_GITHUB_CLIENT_ID:-}
  380. BRIVEN_GITHUB_CLIENT_SECRET: ${BRIVEN_GITHUB_CLIENT_SECRET:-}
  381. BRIVEN_KONNOS_CLIENT_ID: ${BRIVEN_KONNOS_CLIENT_ID:-}
  382. BRIVEN_KONNOS_CLIENT_SECRET: ${BRIVEN_KONNOS_CLIENT_SECRET:-}
  383. BRIVEN_KONNOS_ISSUER: ${BRIVEN_KONNOS_ISSUER:-https://code.konnos.org}
  384. BRIVEN_DISCORD_CLIENT_ID: ${BRIVEN_DISCORD_CLIENT_ID:-}
  385. BRIVEN_DISCORD_CLIENT_SECRET: ${BRIVEN_DISCORD_CLIENT_SECRET:-}
  386. BRIVEN_POLAR_API_BASE: ${BRIVEN_POLAR_API_BASE:-https://api.polar.sh}
  387. BRIVEN_POLAR_ACCESS_TOKEN: ${BRIVEN_POLAR_ACCESS_TOKEN:-}
  388. BRIVEN_POLAR_WEBHOOK_SECRET: ${BRIVEN_POLAR_WEBHOOK_SECRET:-}
  389. BRIVEN_POLAR_PRO_PRODUCT_ID: ${BRIVEN_POLAR_PRO_PRODUCT_ID:-}
  390. BRIVEN_POLAR_TEAM_PRODUCT_ID: ${BRIVEN_POLAR_TEAM_PRODUCT_ID:-}
  391. BRIVEN_DOMAIN: ${BRIVEN_DOMAIN}
  392. BRIVEN_OPEN_SIGNUPS: ${BRIVEN_OPEN_SIGNUPS:-false}
  393. BRIVEN_OLLAMA_URL: ${BRIVEN_OLLAMA_URL:-}
  394. BRIVEN_OLLAMA_API_KEY: ${BRIVEN_OLLAMA_API_KEY:-}
  395. BRIVEN_OLLAMA_MODEL: ${BRIVEN_OLLAMA_MODEL:-qwen2.5-coder:32b}
  396. BRIVEN_MINIO_ENDPOINT: http://minio:9000
  397. BRIVEN_MINIO_PUBLIC_ENDPOINT: https://s3.${BRIVEN_DOMAIN}
  398. BRIVEN_MINIO_ACCESS_KEY: briven
  399. BRIVEN_MINIO_SECRET_KEY: ${BRIVEN_MINIO_ROOT_PASSWORD}
  400. BRIVEN_MINIO_BUCKET: ${BRIVEN_MINIO_BUCKET:-briven}
  401. BRIVEN_MINIO_REGION: ${BRIVEN_MINIO_REGION:-us-east-1}
  402. # imgproxy — on-the-fly image transforms (M4). The api mints SIGNED
  403. # imgproxy URLs (services/image-transform.ts). ENDPOINT is the
  404. # media host + the /_t path prefix (imgproxy sits BEHIND media.<domain>
  405. # under PathPrefix(/_t), no new subdomain). image-transform.ts signs the
  406. # path WITHOUT the prefix (/rs:.../g:sm/<b64 source>) and imgproxy strips
  407. # /_t before verifying — the signed portions match. KEY/SALT are the same
  408. # hex secrets the imgproxy container gets; set all three in the Dokploy
  409. # env to turn transforms on (unset => endpoint returns 503, fail-safe).
  410. BRIVEN_IMGPROXY_ENDPOINT: https://media.${BRIVEN_DOMAIN}/_t
  411. BRIVEN_IMGPROXY_KEY: ${BRIVEN_IMGPROXY_KEY:-}
  412. BRIVEN_IMGPROXY_SALT: ${BRIVEN_IMGPROXY_SALT:-}
  413. # Self-hosted MaxMind GeoLite2-City for Auth email Location (city/country).
  414. # Host file: /var/lib/GeoIP/GeoLite2-City.mmdb (bind-mounted below).
  415. BRIVEN_GEOIP_DB_PATH: ${BRIVEN_GEOIP_DB_PATH:-/var/lib/GeoIP/GeoLite2-City.mmdb}
  416. volumes:
  417. # Read-only city DB for Auth email geo (device location line).
  418. - /var/lib/GeoIP:/var/lib/GeoIP:ro
  419. healthcheck:
  420. # /info is documented to never 500 (apps/api/Dockerfile). bun ships in
  421. # the api image and has a built-in fetch.
  422. test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3001/info').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
  423. interval: 15s
  424. timeout: 5s
  425. retries: 5
  426. start_period: 40s
  427. networks:
  428. - briven
  429. - dokploy-network
  430. labels:
  431. - 'briven_logs=true'
  432. - 'traefik.enable=true'
  433. - 'traefik.docker.network=dokploy-network'
  434. - 'traefik.http.routers.briven-api.rule=Host(`api.${BRIVEN_DOMAIN}`)'
  435. - 'traefik.http.routers.briven-api.entrypoints=websecure'
  436. - 'traefik.http.routers.briven-api.tls.certresolver=letsencrypt'
  437. - 'traefik.http.routers.briven-api.service=briven-api'
  438. - 'traefik.http.services.briven-api.loadbalancer.server.port=3001'
  439. # media.briven.tech -> this api (public file bytes). Explicit .service= on
  440. # every router so Traefik does not treat the container as multi-service.
  441. - 'traefik.http.routers.briven-media.rule=Host(`media.${BRIVEN_DOMAIN}`)'
  442. - 'traefik.http.routers.briven-media.entrypoints=websecure'
  443. - 'traefik.http.routers.briven-media.tls.certresolver=letsencrypt'
  444. - 'traefik.http.routers.briven-media.service=briven-api'
  445. # /_t image transforms: router lives on the imgproxy service (below), not here.
  446. # imgproxy — on-the-fly image transforms (M4). Sits BEHIND media.<domain>
  447. # under the /_t path prefix (no new public subdomain, per the media-host
  448. # rule). The api mints signed URLs; imgproxy fetches the source from the
  449. # PUBLIC media host, resizes, and returns it. SSRF is locked to the media
  450. # host via IMGPROXY_ALLOWED_SOURCES. No host port — only Traefik (on the
  451. # dokploy-network) can reach port 8080.
  452. imgproxy:
  453. image: darthsim/imgproxy:latest
  454. restart: unless-stopped
  455. logging: *briven-logging
  456. environment:
  457. # Signing — MUST equal the api's BRIVEN_IMGPROXY_KEY/SALT (same hex
  458. # secrets, set in the Dokploy env). imgproxy strips IMGPROXY_PATH_PREFIX
  459. # (/_t) from the request path, then verifies the HMAC over the remaining
  460. # /rs:.../g:sm/<b64 source> — exactly what image-transform.ts signed.
  461. IMGPROXY_KEY: ${BRIVEN_IMGPROXY_KEY:-}
  462. IMGPROXY_SALT: ${BRIVEN_IMGPROXY_SALT:-}
  463. IMGPROXY_PATH_PREFIX: /_t
  464. # SSRF guard: imgproxy may ONLY fetch sources from the public media host.
  465. IMGPROXY_ALLOWED_SOURCES: https://media.${BRIVEN_DOMAIN}/
  466. # Sane limits — resize-bomb / oversized-source guards. Format auto-nego:
  467. # serve WebP to browsers that send Accept: image/webp.
  468. IMGPROXY_MAX_SRC_RESOLUTION: '50'
  469. IMGPROXY_ENABLE_WEBP_DETECTION: 'true'
  470. # Bind on 8080 (default) — Traefik reaches it over dokploy-network only.
  471. IMGPROXY_BIND: ':8080'
  472. healthcheck:
  473. # imgproxy ships an /health endpoint that never 500s when the process
  474. # is up. imgproxy has no shell/curl, so use its built-in health probe.
  475. test: ['CMD', 'imgproxy', 'health']
  476. interval: 15s
  477. timeout: 5s
  478. retries: 5
  479. start_period: 20s
  480. networks:
  481. - dokploy-network
  482. labels:
  483. - 'briven_logs=true'
  484. - 'traefik.enable=true'
  485. - 'traefik.docker.network=dokploy-network'
  486. - 'traefik.http.services.briven-imgproxy.loadbalancer.server.port=8080'
  487. # Higher priority than briven-media so /_t/* hits imgproxy; rest of
  488. # media.<domain> stays on the api. IMGPROXY_PATH_PREFIX=/_t strips prefix.
  489. - 'traefik.http.routers.briven-imgt.rule=Host(`media.${BRIVEN_DOMAIN}`) && PathPrefix(`/_t`)'
  490. - 'traefik.http.routers.briven-imgt.priority=100'
  491. - 'traefik.http.routers.briven-imgt.entrypoints=websecure'
  492. - 'traefik.http.routers.briven-imgt.tls.certresolver=letsencrypt'
  493. - 'traefik.http.routers.briven-imgt.service=briven-imgproxy'
  494. runtime:
  495. build:
  496. context: ../..
  497. dockerfile: apps/runtime/Dockerfile
  498. restart: unless-stopped
  499. logging: *briven-logging
  500. depends_on:
  501. api:
  502. condition: service_started
  503. doltgres:
  504. condition: service_healthy
  505. environment:
  506. BRIVEN_ENV: production
  507. BRIVEN_RUNTIME_PORT: '3003'
  508. BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
  509. BRIVEN_RUNTIME_EXECUTOR: deno
  510. BRIVEN_RUNTIME_BUNDLE_DIR: /var/lib/briven/bundles
  511. BRIVEN_API_INTERNAL_URL: http://api:3001
  512. # Data plane — DoltGres (the old BRIVEN_URL mysql:// was wrong, removed).
  513. BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
  514. volumes:
  515. - runtime_bundles:/var/lib/briven/bundles
  516. healthcheck:
  517. # Any HTTP response = process is up (port serving).
  518. test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3003/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
  519. interval: 15s
  520. timeout: 5s
  521. retries: 5
  522. start_period: 40s
  523. networks:
  524. - briven
  525. labels:
  526. - 'briven_logs=true'
  527. realtime:
  528. build:
  529. context: ../..
  530. dockerfile: apps/realtime/Dockerfile
  531. restart: unless-stopped
  532. logging: *briven-logging
  533. depends_on:
  534. doltgres:
  535. condition: service_healthy
  536. environment:
  537. BRIVEN_ENV: production
  538. BRIVEN_REALTIME_PORT: '3004'
  539. BRIVEN_API_INTERNAL_URL: http://api:3001
  540. BRIVEN_RUNTIME_SHARED_SECRET: ${BRIVEN_RUNTIME_SHARED_SECRET}
  541. # Data plane — DoltGres. Realtime polls DOLT_HASHOF('HEAD') per project
  542. # (no LISTEN/NOTIFY on DoltGres). The old BRIVEN_URL mysql:// was wrong.
  543. BRIVEN_DATA_PLANE_URL: postgres://postgres:${BRIVEN_DOLTGRES_PASSWORD}@doltgres:5432/postgres?sslmode=disable
  544. BRIVEN_REALTIME_POLL_MS: '500'
  545. healthcheck:
  546. test: ['CMD-SHELL', "bun -e \"fetch('http://localhost:3004/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
  547. interval: 15s
  548. timeout: 5s
  549. retries: 5
  550. start_period: 40s
  551. networks:
  552. - briven
  553. - dokploy-network
  554. labels:
  555. - 'briven_logs=true'
  556. - 'traefik.enable=true'
  557. - 'traefik.docker.network=dokploy-network'
  558. - 'traefik.http.routers.briven-realtime.rule=Host(`realtime.${BRIVEN_DOMAIN}`)'
  559. - 'traefik.http.routers.briven-realtime.entrypoints=websecure'
  560. - 'traefik.http.routers.briven-realtime.tls.certresolver=letsencrypt'
  561. - 'traefik.http.services.briven-realtime.loadbalancer.server.port=3004'
  562. web:
  563. build:
  564. context: ../..
  565. dockerfile: apps/web/Dockerfile
  566. restart: unless-stopped
  567. logging: *briven-logging
  568. depends_on:
  569. api:
  570. condition: service_started
  571. environment:
  572. BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
  573. BRIVEN_WEB_ORIGIN: https://${BRIVEN_DOMAIN}
  574. NEXT_PUBLIC_BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
  575. NEXT_PUBLIC_BRIVEN_HAS_GOOGLE_OAUTH: ${BRIVEN_GOOGLE_CLIENT_ID:+true}
  576. NEXT_PUBLIC_BRIVEN_HAS_GITHUB_OAUTH: ${BRIVEN_GITHUB_CLIENT_ID:+true}
  577. NEXT_PUBLIC_BRIVEN_HAS_KONNOS_OAUTH: ${BRIVEN_KONNOS_CLIENT_ID:+true}
  578. NEXT_PUBLIC_BRIVEN_HAS_DISCORD_OAUTH: ${BRIVEN_DISCORD_CLIENT_ID:+true}
  579. healthcheck:
  580. # web runs `next start` on node — use node's built-in fetch.
  581. test: ['CMD-SHELL', "node -e \"fetch('http://localhost:3000/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
  582. interval: 15s
  583. timeout: 5s
  584. retries: 5
  585. start_period: 40s
  586. networks:
  587. - briven
  588. - dokploy-network
  589. labels:
  590. - 'briven_logs=true'
  591. - 'traefik.enable=true'
  592. - 'traefik.docker.network=dokploy-network'
  593. - 'traefik.http.routers.briven-web.rule=Host(`${BRIVEN_DOMAIN}`) || Host(`app.${BRIVEN_DOMAIN}`) || Host(`admin.${BRIVEN_DOMAIN}`)'
  594. - 'traefik.http.routers.briven-web.entrypoints=websecure'
  595. - 'traefik.http.routers.briven-web.tls.certresolver=letsencrypt'
  596. - 'traefik.http.routers.briven-web.service=briven-web'
  597. - 'traefik.http.services.briven-web.loadbalancer.server.port=3000'
  598. docs:
  599. build:
  600. context: ../..
  601. dockerfile: apps/docs/Dockerfile
  602. restart: unless-stopped
  603. logging: *briven-logging
  604. depends_on:
  605. api:
  606. condition: service_started
  607. environment:
  608. # Used by /status + /api/status/incidents.xml to read live incidents.
  609. BRIVEN_API_ORIGIN: https://api.${BRIVEN_DOMAIN}
  610. healthcheck:
  611. test: ['CMD-SHELL', "node -e \"fetch('http://localhost:3002/').then(()=>process.exit(0)).catch(()=>process.exit(1))\""]
  612. interval: 15s
  613. timeout: 5s
  614. retries: 5
  615. start_period: 40s
  616. networks:
  617. - briven
  618. - dokploy-network
  619. labels:
  620. - 'briven_logs=true'
  621. - 'traefik.enable=true'
  622. - 'traefik.docker.network=dokploy-network'
  623. - 'traefik.http.routers.briven-docs.rule=Host(`docs.${BRIVEN_DOMAIN}`)'
  624. - 'traefik.http.routers.briven-docs.entrypoints=websecure'
  625. - 'traefik.http.routers.briven-docs.tls.certresolver=letsencrypt'
  626. - 'traefik.http.routers.briven-docs.service=briven-docs'
  627. - 'traefik.http.services.briven-docs.loadbalancer.server.port=3002'
  628. # status.${BRIVEN_DOMAIN} — same docs container; bare `/` rewrites to
  629. # /status. Other paths pass through (so /api/status/incidents.xml works).
  630. - 'traefik.http.routers.briven-status.rule=Host(`status.${BRIVEN_DOMAIN}`)'
  631. - 'traefik.http.routers.briven-status.entrypoints=websecure'
  632. - 'traefik.http.routers.briven-status.tls.certresolver=letsencrypt'
  633. - 'traefik.http.routers.briven-status.service=briven-docs'
  634. - 'traefik.http.routers.briven-status.middlewares=briven-status-rewrite'
  635. - 'traefik.http.middlewares.briven-status-rewrite.replacepathregex.regex=^/$$'
  636. - 'traefik.http.middlewares.briven-status-rewrite.replacepathregex.replacement=/status'
  637. volumes:
  638. postgres_data:
  639. doltgres_data:
  640. doltgres_backups:
  641. redis_data:
  642. minio_data:
  643. runtime_bundles:
  644. networks:
  645. briven:
  646. driver: bridge
  647. # Dokploy's ingress network — Traefik watches this for routing + TLS.
  648. # Routed services (api, realtime, web, docs, s3) attach to it in addition
  649. # to the internal `briven` network; DBs/redis/runtime stay internal-only.
  650. dokploy-network:
  651. external: true