compose.yml296 lines · main
| 1 | # briven data-plane stack — Phase 6 of BACKEND_FORK_BRIEF.md. |
| 2 | # |
| 3 | # Per-project Postgres + REST + Auth + Realtime + Schema-introspection, |
| 4 | # fronted by Caddy. Studio (apps/studio) is deployed separately as a |
| 5 | # Next.js app at studio.briven.tech and talks to this stack via the |
| 6 | # Caddy proxy. |
| 7 | # |
| 8 | # Services dropped from upstream supabase docker-compose: |
| 9 | # - studio (lives in apps/studio, deployed as separate Next app) |
| 10 | # - kong (replaced with caddy — see plan §1) |
| 11 | # - storage / imgproxy (deferred to Phase 9 per brief §5) |
| 12 | # - functions (deferred — briven has separate Deno isolate plan) |
| 13 | # - analytics (logflare) — briven uses Loki+Grafana instead |
| 14 | # - vector (hard-rule violation of docs/DOCKER.md rule 6 — |
| 15 | # bind-mounts /var/run/docker.sock; never re-add) |
| 16 | # - supavisor (deferred to Phase 9 — pool sizing only matters at scale) |
| 17 | # |
| 18 | # Hard rules enforced (docs/DOCKER.md): |
| 19 | # - every service uses logging: *briven-logging (cap 10MB x 3 files) |
| 20 | # - no service mounts /var/run/docker.sock |
| 21 | # - no auto-poll of the Docker API |
| 22 | # - restart: unless-stopped, not always |
| 23 | |
| 24 | x-logging: &briven-logging |
| 25 | driver: json-file |
| 26 | options: |
| 27 | max-size: '10m' |
| 28 | max-file: '3' |
| 29 | |
| 30 | services: |
| 31 | # ── Reverse proxy ──────────────────────────────────────────────────── |
| 32 | # Strips identifying upstream headers (Server, X-Powered-By) before |
| 33 | # any response leaves the trust zone. Adds Server: briven. Auto-TLS |
| 34 | # via Let's Encrypt if BRIVEN_PUBLIC_HOST is set to a routable domain. |
| 35 | caddy: |
| 36 | image: caddy:2.10-alpine |
| 37 | container_name: briven-caddy |
| 38 | restart: unless-stopped |
| 39 | logging: *briven-logging |
| 40 | networks: |
| 41 | - briven-network |
| 42 | ports: |
| 43 | - '${BRIVEN_PROXY_HTTP_PORT:-8000}:80' |
| 44 | - '${BRIVEN_PROXY_HTTPS_PORT:-8443}:443' |
| 45 | environment: |
| 46 | BRIVEN_PUBLIC_HOST: ${BRIVEN_PUBLIC_HOST:-api.briven.tech} |
| 47 | volumes: |
| 48 | - ./volumes/proxy/caddy/Caddyfile:/etc/caddy/Caddyfile:ro,z |
| 49 | - caddy_data:/data |
| 50 | - caddy_config:/config |
| 51 | depends_on: |
| 52 | auth: |
| 53 | condition: service_healthy |
| 54 | rest: |
| 55 | condition: service_healthy |
| 56 | healthcheck: |
| 57 | test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost/'] |
| 58 | interval: 10s |
| 59 | timeout: 5s |
| 60 | retries: 5 |
| 61 | |
| 62 | # ── Postgres ───────────────────────────────────────────────────────── |
| 63 | # The data plane. Schemas per project are created on demand by the |
| 64 | # control-plane provisioning flow (Phase 7). |
| 65 | db: |
| 66 | image: supabase/postgres:15.8.1.085 |
| 67 | container_name: briven-db |
| 68 | restart: unless-stopped |
| 69 | logging: *briven-logging |
| 70 | networks: |
| 71 | - briven-network |
| 72 | ports: |
| 73 | - '${BRIVEN_DB_HOST_PORT:-5432}:5432' |
| 74 | environment: |
| 75 | POSTGRES_HOST: /var/run/postgresql |
| 76 | POSTGRES_PORT: 5432 |
| 77 | POSTGRES_DB: ${BRIVEN_POSTGRES_DB:-postgres} |
| 78 | POSTGRES_PASSWORD: ${BRIVEN_POSTGRES_PASSWORD:?BRIVEN_POSTGRES_PASSWORD is required} |
| 79 | PGPORT: 5432 |
| 80 | PGPASSWORD: ${BRIVEN_POSTGRES_PASSWORD:?BRIVEN_POSTGRES_PASSWORD is required} |
| 81 | PGDATABASE: ${BRIVEN_POSTGRES_DB:-postgres} |
| 82 | JWT_SECRET: ${BRIVEN_JWT_SECRET:?BRIVEN_JWT_SECRET is required} |
| 83 | JWT_EXP: ${BRIVEN_JWT_EXP:-3600} |
| 84 | volumes: |
| 85 | - briven-db-data:/var/lib/postgresql/data |
| 86 | - briven-db-config:/etc/postgresql-custom |
| 87 | - ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z |
| 88 | - ./volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z |
| 89 | - ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z |
| 90 | - ./volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z |
| 91 | - ./volumes/db/_briven.sql:/docker-entrypoint-initdb.d/migrations/97-_briven.sql:Z |
| 92 | - ./volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z |
| 93 | healthcheck: |
| 94 | test: ['CMD', 'pg_isready', '-U', 'postgres', '-h', 'localhost'] |
| 95 | interval: 5s |
| 96 | timeout: 5s |
| 97 | retries: 10 |
| 98 | |
| 99 | # ── Auth (gotrue) ──────────────────────────────────────────────────── |
| 100 | # GOTRUE_* env names are baked into the upstream Go binary; we wrap |
| 101 | # them with BRIVEN_AUTH_* indirection at the compose layer. Caddy |
| 102 | # strips response headers (Server, X-Powered-By, Via) — see |
| 103 | # volumes/proxy/caddy/Caddyfile. Response BODY scrubbing of upstream |
| 104 | # identifiers (gotrue error messages, postgrest hints) is not yet |
| 105 | # implemented — vanilla caddy:2-alpine lacks the replace-response |
| 106 | # module. Tracked in HANDOFF.md §"Carry-over gaps". |
| 107 | auth: |
| 108 | image: supabase/gotrue:v2.186.0 |
| 109 | container_name: briven-auth |
| 110 | restart: unless-stopped |
| 111 | logging: *briven-logging |
| 112 | networks: |
| 113 | - briven-network |
| 114 | depends_on: |
| 115 | db: |
| 116 | condition: service_healthy |
| 117 | environment: |
| 118 | GOTRUE_API_HOST: 0.0.0.0 |
| 119 | GOTRUE_API_PORT: 9999 |
| 120 | API_EXTERNAL_URL: ${BRIVEN_API_EXTERNAL_URL:?BRIVEN_API_EXTERNAL_URL is required} |
| 121 | GOTRUE_DB_DRIVER: postgres |
| 122 | GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${BRIVEN_POSTGRES_PASSWORD}@db:5432/${BRIVEN_POSTGRES_DB:-postgres} |
| 123 | GOTRUE_SITE_URL: ${BRIVEN_AUTH_SITE_URL:?BRIVEN_AUTH_SITE_URL is required} |
| 124 | GOTRUE_URI_ALLOW_LIST: ${BRIVEN_AUTH_URI_ALLOW_LIST:-} |
| 125 | GOTRUE_DISABLE_SIGNUP: ${BRIVEN_AUTH_DISABLE_SIGNUP:-false} |
| 126 | GOTRUE_JWT_ADMIN_ROLES: service_role |
| 127 | GOTRUE_JWT_AUD: authenticated |
| 128 | GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated |
| 129 | GOTRUE_JWT_EXP: ${BRIVEN_JWT_EXP:-3600} |
| 130 | GOTRUE_JWT_SECRET: ${BRIVEN_JWT_SECRET} |
| 131 | GOTRUE_EXTERNAL_EMAIL_ENABLED: ${BRIVEN_AUTH_EMAIL_ENABLED:-true} |
| 132 | GOTRUE_MAILER_AUTOCONFIRM: ${BRIVEN_AUTH_MAILER_AUTOCONFIRM:-false} |
| 133 | GOTRUE_SMTP_HOST: ${BRIVEN_AUTH_SMTP_HOST:-} |
| 134 | GOTRUE_SMTP_PORT: ${BRIVEN_AUTH_SMTP_PORT:-587} |
| 135 | GOTRUE_SMTP_USER: ${BRIVEN_AUTH_SMTP_USER:-} |
| 136 | GOTRUE_SMTP_PASS: ${BRIVEN_AUTH_SMTP_PASS:-} |
| 137 | GOTRUE_SMTP_ADMIN_EMAIL: ${BRIVEN_AUTH_SMTP_ADMIN_EMAIL:-} |
| 138 | GOTRUE_SMTP_SENDER_NAME: ${BRIVEN_AUTH_SMTP_SENDER_NAME:-briven} |
| 139 | healthcheck: |
| 140 | test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:9999/health'] |
| 141 | interval: 5s |
| 142 | timeout: 5s |
| 143 | retries: 10 |
| 144 | |
| 145 | # ── PostgREST ──────────────────────────────────────────────────────── |
| 146 | # Auto-generates a REST API from the DB schema. PGRST_* names are |
| 147 | # native; wrapped via BRIVEN_REST_* at the compose layer. |
| 148 | rest: |
| 149 | image: postgrest/postgrest:v14.8 |
| 150 | container_name: briven-rest |
| 151 | restart: unless-stopped |
| 152 | logging: *briven-logging |
| 153 | networks: |
| 154 | - briven-network |
| 155 | depends_on: |
| 156 | db: |
| 157 | condition: service_healthy |
| 158 | environment: |
| 159 | PGRST_DB_URI: postgres://authenticator:${BRIVEN_POSTGRES_PASSWORD}@db:5432/${BRIVEN_POSTGRES_DB:-postgres} |
| 160 | PGRST_DB_SCHEMAS: ${BRIVEN_REST_DB_SCHEMAS:-public,storage,graphql_public} |
| 161 | PGRST_DB_ANON_ROLE: anon |
| 162 | PGRST_JWT_SECRET: ${BRIVEN_JWT_SECRET} |
| 163 | PGRST_DB_USE_LEGACY_GUCS: 'false' |
| 164 | PGRST_APP_SETTINGS_JWT_SECRET: ${BRIVEN_JWT_SECRET} |
| 165 | PGRST_APP_SETTINGS_JWT_EXP: ${BRIVEN_JWT_EXP:-3600} |
| 166 | command: |
| 167 | - 'postgrest' |
| 168 | healthcheck: |
| 169 | test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:3000/'] |
| 170 | interval: 5s |
| 171 | timeout: 5s |
| 172 | retries: 10 |
| 173 | |
| 174 | # ── Realtime (Phoenix) ─────────────────────────────────────────────── |
| 175 | # WebSocket subscriptions on Postgres logical replication. Native env |
| 176 | # names (API_JWT_SECRET, DB_HOST, etc.) wrapped via BRIVEN_REALTIME_*. |
| 177 | realtime: |
| 178 | image: supabase/realtime:v2.76.5 |
| 179 | container_name: briven-realtime |
| 180 | restart: unless-stopped |
| 181 | logging: *briven-logging |
| 182 | networks: |
| 183 | - briven-network |
| 184 | depends_on: |
| 185 | db: |
| 186 | condition: service_healthy |
| 187 | environment: |
| 188 | PORT: 4000 |
| 189 | DB_HOST: db |
| 190 | DB_PORT: 5432 |
| 191 | DB_USER: supabase_admin |
| 192 | DB_PASSWORD: ${BRIVEN_POSTGRES_PASSWORD} |
| 193 | DB_NAME: ${BRIVEN_POSTGRES_DB:-postgres} |
| 194 | DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' |
| 195 | DB_ENC_KEY: ${BRIVEN_REALTIME_ENC_KEY:?BRIVEN_REALTIME_ENC_KEY is required} |
| 196 | API_JWT_SECRET: ${BRIVEN_JWT_SECRET} |
| 197 | SECRET_KEY_BASE: ${BRIVEN_REALTIME_SECRET_KEY_BASE:?BRIVEN_REALTIME_SECRET_KEY_BASE is required} |
| 198 | ERL_AFLAGS: -proto_dist inet_tcp |
| 199 | DNS_NODES: "''" |
| 200 | RLIMIT_NOFILE: '10000' |
| 201 | APP_NAME: realtime |
| 202 | SEED_SELF_HOST: 'true' |
| 203 | RUN_JANITOR: 'true' |
| 204 | healthcheck: |
| 205 | test: |
| 206 | - CMD-SHELL |
| 207 | - 'curl -sSfL --head -o /dev/null -H "Authorization: Bearer ${BRIVEN_JWT_SECRET}" http://localhost:4000/api/tenants/realtime-dev/health' |
| 208 | interval: 5s |
| 209 | timeout: 5s |
| 210 | retries: 3 |
| 211 | |
| 212 | # ── postgres-meta ──────────────────────────────────────────────────── |
| 213 | # Schema introspection backend that Studio reads from. PG_META_* native; |
| 214 | # wrapped via BRIVEN_META_*. |
| 215 | meta: |
| 216 | image: supabase/postgres-meta:v0.96.3 |
| 217 | container_name: briven-meta |
| 218 | restart: unless-stopped |
| 219 | logging: *briven-logging |
| 220 | networks: |
| 221 | - briven-network |
| 222 | depends_on: |
| 223 | db: |
| 224 | condition: service_healthy |
| 225 | environment: |
| 226 | PG_META_PORT: 8080 |
| 227 | PG_META_DB_HOST: db |
| 228 | PG_META_DB_PORT: 5432 |
| 229 | PG_META_DB_NAME: ${BRIVEN_POSTGRES_DB:-postgres} |
| 230 | PG_META_DB_USER: supabase_admin |
| 231 | PG_META_DB_PASSWORD: ${BRIVEN_POSTGRES_PASSWORD} |
| 232 | healthcheck: |
| 233 | test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:8080/health'] |
| 234 | interval: 10s |
| 235 | timeout: 5s |
| 236 | retries: 5 |
| 237 | |
| 238 | # ── pgBackRest ─────────────────────────────────────────────────────── |
| 239 | # Phase 9 of BACKEND_FORK_BRIEF.md. Continuous WAL archive + incremental |
| 240 | # backups to Cloudflare R2 every 15 minutes; full backup nightly. Reads |
| 241 | # PGDATA via a shared bind-mount with the db service (NOT via the |
| 242 | # daemon API). Restore procedure documented in infra/datapane/RESTORE.md. |
| 243 | pgbackrest: |
| 244 | image: pgbackrest/pgbackrest:2.55.1 |
| 245 | container_name: briven-pgbackrest |
| 246 | restart: unless-stopped |
| 247 | logging: *briven-logging |
| 248 | networks: |
| 249 | - briven-network |
| 250 | depends_on: |
| 251 | db: |
| 252 | condition: service_healthy |
| 253 | environment: |
| 254 | PGBACKREST_REPO1_TYPE: s3 |
| 255 | PGBACKREST_REPO1_S3_BUCKET: ${BRIVEN_BACKUP_R2_BUCKET:?BRIVEN_BACKUP_R2_BUCKET is required} |
| 256 | PGBACKREST_REPO1_S3_ENDPOINT: ${BRIVEN_BACKUP_R2_ENDPOINT:?BRIVEN_BACKUP_R2_ENDPOINT is required} |
| 257 | PGBACKREST_REPO1_S3_REGION: auto |
| 258 | PGBACKREST_REPO1_S3_KEY: ${BRIVEN_BACKUP_R2_ACCESS_KEY:?BRIVEN_BACKUP_R2_ACCESS_KEY is required} |
| 259 | PGBACKREST_REPO1_S3_KEY_SECRET: ${BRIVEN_BACKUP_R2_SECRET_KEY:?BRIVEN_BACKUP_R2_SECRET_KEY is required} |
| 260 | PGBACKREST_REPO1_RETENTION_FULL: '4' |
| 261 | PGBACKREST_REPO1_RETENTION_DIFF: '7' |
| 262 | PGBACKREST_REPO1_CIPHER_TYPE: aes-256-cbc |
| 263 | PGBACKREST_REPO1_CIPHER_PASS: ${BRIVEN_BACKUP_ENCRYPTION_KEY:?BRIVEN_BACKUP_ENCRYPTION_KEY is required} |
| 264 | PGBACKREST_PROCESS_MAX: '2' |
| 265 | PGBACKREST_COMPRESS_TYPE: zst |
| 266 | PGBACKREST_LOG_LEVEL_FILE: info |
| 267 | PGBACKREST_LOG_LEVEL_CONSOLE: warn |
| 268 | volumes: |
| 269 | - briven-db-data:/var/lib/postgresql/data:ro |
| 270 | - briven-backup-cache:/var/lib/pgbackrest |
| 271 | - ./pgbackrest/pgbackrest.conf:/etc/pgbackrest/pgbackrest.conf:ro,z |
| 272 | - ./pgbackrest/crontab:/etc/crontabs/root:ro,z |
| 273 | - ./pgbackrest/entrypoint.sh:/entrypoint.sh:ro,z |
| 274 | entrypoint: ['/bin/sh'] |
| 275 | command: ['/entrypoint.sh'] |
| 276 | healthcheck: |
| 277 | test: ['CMD', 'pgbackrest', '--stanza=briven', 'check'] |
| 278 | interval: 1m |
| 279 | timeout: 30s |
| 280 | retries: 3 |
| 281 | |
| 282 | networks: |
| 283 | briven-network: |
| 284 | name: briven-network |
| 285 | |
| 286 | volumes: |
| 287 | briven-db-data: |
| 288 | name: briven-db-data |
| 289 | briven-db-config: |
| 290 | name: briven-db-config |
| 291 | briven-backup-cache: |
| 292 | name: briven-backup-cache |
| 293 | caddy_data: |
| 294 | name: briven-caddy-data |
| 295 | caddy_config: |
| 296 | name: briven-caddy-config |