-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
287 lines (278 loc) · 11.2 KB
/
Copy pathdocker-compose.yml
File metadata and controls
287 lines (278 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# ---------------------------------------------------------------------------
# WARNING: the default credentials in this file are for local development
# only. The ONLY secret operators set directly is POSTGRES_PASSWORD. All
# cryptographic keys are generated by the trex-init service on first boot
# and written to ./secrets/{root.env,derived.env}. Mount that directory as
# a secret-grade volume in production and back it up — losing root.env
# invalidates every encrypted secret in trex.{secret,database_credential}
# and every issued JWT.
# ---------------------------------------------------------------------------
# Two-node cluster: trex-data (data node, holds the pool + Flight) + trex-server
# (non-data node, serves the UI/API and opens remote sessions to trex-data).
# Gossip seeds are auto-derived from the `nodes` map below, so the two converge
# automatically. Flight binds 0.0.0.0 but the db extension advertises it to
# gossip under each node's gossip host (trex-data / trex-server) so peers get a
# routable endpoint — advertising 0.0.0.0 made the server dial itself, so every
# data-backed resolver failed with "create_session failed" and the UI hung.
x-swarm-config: &swarm-config
SWARM_CONFIG: >-
{"cluster_id":"local","nodes":{
"data":{
"gossip_addr":"trex-data:4200","data_node":true,
"extensions":[
{"name":"flight","config":{"host":"0.0.0.0","port":50051}}
]},
"server":{
"gossip_addr":"trex-server:4200","data_node":false,
"extensions":[
{"name":"trexas","config":{"host":"0.0.0.0","port":8001,"main_service_path":"/usr/src/core/server/index.eszip","event_worker_path":"/usr/src/core/event/index.eszip","tls_port":8000,"tls_cert_path":"/usr/src/server.crt","tls_key_path":"/usr/src/server.key"}},
{"name":"pgwire","config":{"host":"0.0.0.0","port":5432}}
]}
}}
volumes:
core-pgdata:
claude-config:
gh-config:
studio-snippets:
studio-edge-functions:
services:
trex-init:
image: ghcr.io/ohdsi/trexsql:latest
entrypoint: /usr/local/bin/trex-init
environment:
TREX_SECRETS_DIR: /shared
volumes:
- ./secrets:/shared
restart: "no"
postgres:
image: postgres:16
container_name: trexsql-postgres
ports:
- 65433:5432
command:
- postgres
- -c
- wal_level=logical
- -c
- max_replication_slots=10
- -c
- max_wal_senders=10
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mypass}
POSTGRES_DB: testdb
volumes:
- core-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
trex-data:
image: ghcr.io/ohdsi/trexsql:latest
volumes:
- claude-config:/home/node/.claude
- gh-config:/home/node/.config/gh
# core/server, core/schema and the plugins (web, studio, storage, pg-meta)
# are built into the image (see the builder stages in the Dockerfile), so
# we do NOT bind-mount host source over them here — doing so shadowed the
# built artifacts (e.g. web/dist) and broke the UIs. For live development
# against host source, use docker-compose.dev.yml, which mounts only the
# built outputs (e.g. ./plugins/web/dist).
# Shared with the studio sidecar: Studio's deploy writes here, trex reads.
- studio-edge-functions:/app/edge-functions
depends_on:
trex-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
env_file:
- path: ./secrets/root.env
required: false
- path: ./secrets/derived.env
required: false
environment:
<<: *swarm-config
SWARM_NODE: data
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-mypass}@postgres:5432/testdb
SCHEMA_DIR: /usr/src/core/schema
PLUGINS_PATH: /usr/src/plugins
PLUGINS_DEV_PATH: /usr/src/plugins-dev
healthcheck:
# Probe the flight gRPC port with node (the image ships node, not nc).
test:
- CMD-SHELL
- "node -e \"require('net').connect(50051,'127.0.0.1').on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))\""
interval: 5s
timeout: 3s
retries: 30
start_period: 30s
trex-server:
image: ghcr.io/ohdsi/trexsql:latest
ports:
- 8000:8000
- 8001:8001
- 5433:5432
volumes:
- claude-config:/home/node/.claude
- gh-config:/home/node/.config/gh
# Plugins (web, studio, storage, pg-meta) and core/server are built into
# the image; not bind-mounting host source over them (that shadowed the
# built artifacts and broke the UIs). Use docker-compose.dev.yml for
# live development against host source.
# Shared with the studio sidecar: Studio's deploy writes here, trex reads.
- studio-edge-functions:/app/edge-functions
depends_on:
trex-init:
condition: service_completed_successfully
trex-data:
condition: service_healthy
env_file:
- path: ./secrets/root.env
required: false
- path: ./secrets/derived.env
required: false
environment:
<<: *swarm-config
SWARM_NODE: server
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-mypass}@postgres:5432/testdb
BASE_PATH: /trex
BETTER_AUTH_URL: http://localhost:8001/trex
PLUGINS_INFORMATION_URL: "https://feeds.dev.azure.com/data2evidence/d2e/_apis/packaging/Feeds/d2e/packages?api-version=7.1&includeDescription=true"
TPM_REGISTRY_URL: "https://pkgs.dev.azure.com/data2evidence/d2e/_packaging/d2e/npm/registry"
PLUGINS_PATH: /usr/src/plugins
PLUGINS_DEV_PATH: /usr/src/plugins-dev
POSTGREST_HOST: postgrest
POSTGREST_PORT: "3000"
STUDIO_INTERNAL_URL: http://studio:3000
EDGE_FUNCTIONS_MANAGEMENT_FOLDER: /app/edge-functions
healthcheck:
# /trex/api/ready returns 503 until supabaseEnvVars is populated, which is
# set immediately after ensureAuthKeys() writes anon/service keys to
# trex.setting. The auth/v1/health probe wasn't enough — it answered 200
# as soon as the HTTP server listened, which could be a few ms before the
# keys hit the DB. studio's depends_on then raced and read empty keys.
test:
- CMD-SHELL
- 'node -e "fetch(''http://localhost:8001/trex/api/ready'').then(r => { if (r.status !== 200) throw new Error(r.status) })"'
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
postgrest:
image: postgrest/postgrest:v12.2.3
# postgrest connects as the `authenticator` role, which is created by
# trex-data's schema migrations — not by postgres or trex-init. Waiting only
# on postgres raced ahead of the migrations and crashed with "password
# authentication failed for user authenticator" (postgrest has no internal
# retry budget for a missing role, so it exited 1). Gate on trex-data being
# healthy (migrations done) and restart as a safety net for any remaining race.
restart: unless-stopped
depends_on:
trex-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
trex-data:
condition: service_healthy
env_file:
- path: ./secrets/derived.env
required: false
environment:
PGRST_DB_URI: postgres://authenticator:authenticator_pass@postgres:5432/testdb
PGRST_DB_SCHEMAS: public
PGRST_DB_ANON_ROLE: anon
# PGRST_JWT_SECRET comes from ./secrets/derived.env (see env_file above).
PGRST_DB_PRE_REQUEST: public.postgrest_pre_request
PGRST_DB_USE_LEGACY_GUCS: "false"
PGRST_OPENAPI_SERVER_PROXY_URI: http://localhost:8001/trex/rest/v1
# Studio sidecar — internal-only; trex proxies /plugins/trex/studio/** to it.
# Runs from the published image (the trex-specific entrypoint + key-fetch
# helper are baked in). To build/refresh it locally for testing:
# docker build plugins/studio/supabase-studio \
# -f plugins/studio/supabase-studio/apps/studio/Dockerfile \
# --target production \
# --build-arg NEXT_PUBLIC_BASE_PATH=/plugins/trex/studio \
# -t ghcr.io/ohdsi/trex-studio:latest
studio:
image: ghcr.io/ohdsi/trex-studio:latest
container_name: trex-studio
restart: unless-stopped
# NOT exposing a host port: direct access would bypass trex's admin-only gate.
depends_on:
trex-init:
condition: service_completed_successfully
trex-server:
condition: service_healthy
volumes:
# The wrapper entrypoint + fetch-trex-keys.cjs (which read
# SUPABASE_ANON_KEY / SUPABASE_SERVICE_KEY from trex.setting at boot) are
# baked into the image, so no host bind mounts are needed here.
- studio-snippets:/app/snippets
- studio-edge-functions:/app/edge-functions
healthcheck:
test:
- CMD-SHELL
- 'node -e "fetch(''http://localhost:3000/plugins/trex/studio/api/platform/profile'').then(r => { if (r.status !== 200) throw new Error(r.status) })"'
interval: 10s
timeout: 5s
retries: 5
env_file:
- path: ./secrets/derived.env
required: false
environment:
HOSTNAME: "0.0.0.0"
STUDIO_PG_META_URL: http://trex-server:8001/trex/pg/v1
POSTGRES_HOST: postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: testdb
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mypass}
# Override Studio's self-hosted defaults — trex's postgres has no supabase_admin user.
POSTGRES_USER_READ_WRITE: postgres
POSTGRES_USER_READ_ONLY: postgres
# PG_META_CRYPTO_KEY and AUTH_JWT_SECRET come from ./secrets/derived.env.
PGRST_DB_SCHEMAS: public,storage
DEFAULT_ORGANIZATION_NAME: trex
DEFAULT_PROJECT_NAME: default
SUPABASE_URL: http://trex-server:8001/trex
SUPABASE_PUBLIC_URL: http://localhost:8001/trex
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY:-}
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY:-}
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
NEXT_PUBLIC_ENABLE_LOGS: "false"
SNIPPETS_MANAGEMENT_FOLDER: /app/snippets
EDGE_FUNCTIONS_MANAGEMENT_FOLDER: /app/edge-functions
realtime:
image: supabase/realtime:v2.93.3
depends_on:
trex-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
env_file:
- path: ./secrets/derived.env
required: false
environment:
PORT: "4000"
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: testdb
DB_USER: supabase_admin
DB_PASSWORD: ${REALTIME_DB_PASSWORD:-realtime_admin_pass}
DB_AFTER_CONNECT_QUERY: SET search_path TO _realtime
# DB_ENC_KEY, API_JWT_SECRET, METRICS_JWT_SECRET, SECRET_KEY_BASE come
# from ./secrets/derived.env (see env_file above).
ERL_AFLAGS: -proto_dist inet_tcp
DNS_NODES: "''"
RLIMIT_NOFILE: "10000"
APP_NAME: realtime
SEED_SELF_HOST: "true"
RUN_JANITOR: "true"
healthcheck:
# Realtime serves an unauthenticated landing page on /; getting 200 here
# means Cowboy is up and the runtime booted past Ecto migrations.
test: ["CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://localhost:4000/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s