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
24x-logging: &briven-logging
25 driver: json-file
26 options:
27 max-size: '10m'
28 max-file: '3'
29
30services:
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
282networks:
283 briven-network:
284 name: briven-network
285
286volumes:
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