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. |
| 54 | name: briven |
| 55 | |
| 56 | x-logging: &briven-logging |
| 57 | driver: json-file |
| 58 | options: |
| 59 | max-size: '10m' |
| 60 | max-file: '3' |
| 61 | |
| 62 | services: |
| 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 | |
| 528 | volumes: |
| 529 | postgres_data: |
| 530 | doltgres_data: |
| 531 | doltgres_backups: |
| 532 | redis_data: |
| 533 | minio_data: |
| 534 | runtime_bundles: |
| 535 | |
| 536 | networks: |
| 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 |