-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
338 lines (327 loc) · 15.8 KB
/
Copy pathdocker-compose.yml
File metadata and controls
338 lines (327 loc) · 15.8 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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# Self-contained stack: the trex backend serving the sibyl shell (with its
# strategus/network/results-viewer sub-plugins) at /plugins/sibyl, with trex as
# the identity provider. The trex image is built from ./Dockerfile, which
# compiles ALL plugins from source — including the results-viewer WebR/shinylive
# R runtime — and bakes them in, so no host npm/R steps or bind-mounts are needed.
#
# docker compose up -d --build # build the image + run the stack
# open http://localhost:8011/plugins/sibyl # 8011 = HTTP (8010 = TLS)
#
# Topology (mirrors trex's own prod docker-compose since the hades-era #38 build):
# trex runs as TWO swarm nodes sharing one image —
# trex-data : data_node, runs the `flight` data plane (:50051) and the core
# schema migrations. The DEK / trexdb.* tables only resolve when a
# data node has formed the cluster.
# trex-server : runs `trexas` (HTTP :8001) + `pgwire`; serves the baked sibyl
# dist + plugins; finds trex-data via gossip. A single-node config
# boots but fails DEK init (trexdb.kek_wrapped_dek unresolved),
# so the split is required.
# Cryptographic keys are generated by trex-init into ./secrets on first boot.
#
# METADATA_ENC_KEY: base64 32-byte AES-256-GCM key for CDM-credential encryption.
# Generate once: openssl rand -base64 32 (export it before `docker compose up`)
# Shared 2-node cluster definition — both trex services reference this so they
# discover each other via gossip on the compose-network hostnames.
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","event_worker_path":"/usr/src/core/event","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}}
]}
}}
name: sibyl
volumes:
pgdata:
# Shared hades working tree: R envs, analysis specs, and run outputs. Mounted
# on BOTH trex-data and trex-server so files written from whichever node runs
# the Rscript (the node holding the DuckDB/pgwire connection that called
# hades_execute) are visible to the other node and to the hades-api function.
hades-data:
# PGDATA for broadsea-atlasdb (OMOP CDM). Named (not bind) so Docker seeds it
# from the image's baked PGDATA on first boot.
atlasdb-data:
# Supabase file-storage backend for published analysis results. The storage
# file backend defaults to an ephemeral /tmp path; a named volume persists the
# result zips across `trex-server` restarts (see FILE_STORAGE_BACKEND_PATH).
storage-data:
services:
# Generates TREX_ROOT_KEY + derived secrets into ./secrets on first boot.
# Pinned to the same trex tag as the Dockerfile FROM so init and runtime
# use one trex version.
trex-init:
container_name: sibyl-trex-init
# Pinned to the same multi-arch index digest as the Dockerfile FROM (=
# trexsql:latest); each host pulls its native variant (amd64 or arm64).
image: ghcr.io/ohdsi/trexsql:latest@sha256:6c3ec02c884766fd52b27c34b3dcd1a12cd385efa74edbfba03d620018794733
entrypoint: /usr/local/bin/trex-init
environment:
TREX_SECRETS_DIR: /shared
volumes:
- ./secrets:/shared
restart: 'no'
# Applies plugin SQL migrations (notebook schema) into the trex _config DB
# (= the postgres `testdb`). trex's plugin loader does NOT auto-run
# `trex.migrations` in this 2-node split (server skips as non-data-node;
# data node runs only core migrations), so we apply them here with the
# postgres client image. Idempotent (CREATE ... IF NOT EXISTS) so re-runs are
# safe. Runs after trex-data has applied core migrations so `_config` exists.
metadata-migrate:
container_name: sibyl-metadata-migrate
image: postgres:16
depends_on:
trex-data:
condition: service_healthy
environment:
PGPASSWORD: ${POSTGRES_PASSWORD:-mypass}
volumes:
- ./plugins/metadata-api/migrations:/migrations/metadata:ro
- ./plugins/network-api/migrations:/migrations/network:ro
entrypoint:
- /bin/sh
- -c
- 'for f in /migrations/metadata/V*.sql /migrations/network/V*.sql; do echo "applying $$f"; psql -h postgres -U postgres -d testdb -v ON_ERROR_STOP=1 -f "$$f" || exit 1; done'
restart: 'no'
# Pre-chowns the storage named volume to uid 1000 (node) so trex-server — which
# runs as node and can't chown a root-owned volume — can write published result
# zips. Runs as root (default for the plain postgres image). One-shot.
storage-init:
container_name: sibyl-storage-init
image: postgres:16
user: root
volumes:
- storage-data:/data/storage
entrypoint:
- /bin/sh
- -c
- 'mkdir -p /data/storage && chown -R 1000:1000 /data/storage'
restart: 'no'
postgres:
container_name: sibyl-postgres
image: postgres:16
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}
# Must be `testdb`: trex's baked V1__initial_schema migration hardcodes
# `GRANT ALL PRIVILEGES ON DATABASE testdb`, so the database cannot be
# renamed without patching the image. Isolation from trex's own stack
# comes from the distinct project/container names + separate postgres
# container & volume, not the DB name.
POSTGRES_DB: testdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 10
# Data node: runs the flight data plane + core schema migrations. Built from
# ./Dockerfile (same image as trex-server) so a single build serves both.
trex-data:
container_name: sibyl-trex-data
build:
context: .
dockerfile: Dockerfile
# trexsql:latest is now multi-arch, so build natively for the host arch
# (arm64 on Apple Silicon, amd64 elsewhere) — no forced platform / QEMU.
# GitHub Packages auth for @ohdsi/atlas-ui (see Dockerfile web-builder).
# Export NODE_AUTH_TOKEN (a GitHub PAT with read:packages) before building.
secrets:
- ghtoken
image: sibyl-trex:local
depends_on:
trex-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
# Source the generated secrets at runtime (after trex-init wrote them);
# env_file is read at container-create time, before first-boot init runs.
entrypoint:
- /bin/sh
- -c
- 'set -a; [ -f /shared/root.env ] && . /shared/root.env; [ -f /shared/derived.env ] && . /shared/derived.env; set +a; mkdir -p /data/hades/envs /data/hades/runs && chown -R 1000:1000 /data/hades 2>/dev/null || true; exec /usr/src/entrypoint.sh'
environment:
<<: *swarm-config
SWARM_NODE: data
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-mypass}@postgres:5432/testdb
# Expose the notebook schema (shares the image with trex-server; harmless
# here since trex-data doesn't serve GraphQL, but keeps the two nodes
# consistent and the plugin migration applies on either node).
PG_SCHEMA: ${PG_SCHEMA:-trexdb,notebook}
SCHEMA_DIR: /usr/src/core/schema
PLUGINS_PATH: /usr/src/plugins
PLUGINS_DEV_PATH: /usr/src/plugins-dev
volumes:
- ./secrets:/shared:ro
# Shared hades working tree (see top-level `hades-data` volume).
- hades-data:/data/hades
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
# Server node: trexas (HTTP) + pgwire; serves the baked sibyl dist + plugins.
trex-server:
container_name: sibyl-trex
image: sibyl-trex:local
# Distinct host ports from trex's own docker-compose (8001/8000/5433) so
# both stacks can run in parallel. Container ports are unchanged.
ports:
- 8011:8001 # HTTP (web UI, plugins, auth, graphql)
- 8010:8000 # TLS
- 5443:5432 # Postgres wire
depends_on:
trex-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
trex-data:
condition: service_healthy
storage-init:
condition: service_completed_successfully
# Source the generated secrets at runtime, then regenerate the network
# sub-plugin's browser config from NETWORK_* env, then start trex.
# NOTE: trex runs as uid 1000 (node), which can't chown a root-owned named
# volume — so the storage volume is pre-chowned by the root `storage-init`
# service above (the inline chown here is a best-effort no-op under node).
entrypoint:
- /bin/sh
- -c
- 'set -a; [ -f /shared/root.env ] && . /shared/root.env; [ -f /shared/derived.env ] && . /shared/derived.env; set +a; mkdir -p /data/hades/envs /data/hades/runs /data/storage 2>/dev/null || true; /usr/local/bin/write-network-config.sh; exec /usr/src/entrypoint.sh'
environment:
<<: *swarm-config
SWARM_NODE: server
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-mypass}@postgres:5432/testdb
BASE_PATH: /trex
BETTER_AUTH_URL: http://localhost:8011/trex
# Admin bootstrap, same convention as trex: the sign-up matching
# ADMIN_EMAIL (or simply the first user) is promoted to admin. Override
# with `ADMIN_EMAIL=you@example.com docker compose up -d`.
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@ohdsi.local}
PLUGINS_PATH: /usr/src/plugins
PLUGINS_DEV_PATH: /usr/src/plugins-dev
SCHEMA_DIR: /usr/src/core/schema
# Notebook metadata: expose the notebook schema via GraphQL, enable GraphiQL
# for dev, and provide the AES key for CDM-credential encryption.
PG_SCHEMA: ${PG_SCHEMA:-trexdb,notebook}
ENABLE_GRAPHIQL: "true"
METADATA_ENC_KEY: ${METADATA_ENC_KEY:-}
TREX_BASE_URL: http://localhost:8001/trex
# Persist published analysis-result files on the storage-data volume so they
# survive trex-server restarts (the storage file backend is otherwise
# ephemeral under /tmp).
FILE_STORAGE_BACKEND_PATH: /data/storage
# hades-api function plugin config. The hades DuckDB extension runs the
# Rscript on the node holding the SQL connection (here, trex-server, which
# runs pgwire), so these paths must live on the shared `hades-data` volume
# mounted on both nodes. The hades-api function passes these as the
# base_dir / output_path / work_schema args to hades_setup_env/execute.
HADES_ENVS_BASE_DIR: /data/hades/envs
HADES_OUTPUT_BASE_DIR: /data/hades/runs
# trexsql namespaces all persisted data (incl. the baked demo CDM and the
# work schema) under the `_config` catalog; the pgwire default catalog is
# the empty `memory`, so hades must use catalog-qualified schema names.
HADES_WORK_SCHEMA: _config.work
# network-api function plugin (server-side machine auth). The browser
# network plugin has NO in-browser login — all its API calls go through the
# network-api trex function, which uses the per-site CONFIDENTIAL Cognito
# client (client-credentials) to call the central API. The secret stays in
# the function worker (injected via trex.functions.env) and never reaches
# the browser. Values come from the central `POST /sites` registration
# (clientId/clientSecret, shown once) + stack outputs:
# NETWORK_API_URL <- ApiUrl (central base the fn proxies to)
# NETWORK_COGNITO_DOMAIN <- CognitoDomain
# NETWORK_MACHINE_CLIENT_ID<- per-site confidential clientId
# NETWORK_CLIENT_SECRET <- per-site confidential clientSecret
NETWORK_API_URL: ${NETWORK_API_URL:-}
NETWORK_COGNITO_DOMAIN: ${NETWORK_COGNITO_DOMAIN:-}
NETWORK_MACHINE_CLIENT_ID: ${NETWORK_MACHINE_CLIENT_ID:-}
NETWORK_CLIENT_SECRET: ${NETWORK_CLIENT_SECRET:-}
NETWORK_TOKEN_SCOPE: ${NETWORK_TOKEN_SCOPE:-}
# AES-256-GCM key (base64 32 bytes) the network-api function uses to encrypt
# the per-site machine secret at rest in network.site_credential after a
# self-signup is approved and the node claims its credentials.
NETWORK_ENC_KEY: ${NETWORK_ENC_KEY:-}
# Optional: override the browser's proxy base (defaults to the plugin's
# same-origin /plugins/network-api/network-api mount). Written into
# window.__networkPluginConfig by write-network-config.sh.
NETWORK_PROXY_URL: ${NETWORK_PROXY_URL:-}
volumes:
# sibyl is baked into the image by Dockerfile — see the `build:` above.
# Only the generated secrets are mounted at runtime.
- ./secrets:/shared:ro
# Shared hades working tree (see top-level `hades-data` volume). Must match
# the mount on trex-data so the Rscript outputs are cross-node visible.
- hades-data:/data/hades
# Persistent storage backend for published analysis-result files.
- storage-data:/data/storage
healthcheck:
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
# OHDSI Broadsea atlasdb — Postgres preloaded with a synthetic OMOP CDM
# (Eunomia/Synthea demo: schemas demo_cdm, demo_cdm_results, vocabulary, plus
# the webapi `ohdsi` schema). Source of CDM data for HADES/Strategus test runs.
# Pattern copied from ../d2e (services demodb / test-broadsea-atlasdb).
#
# Opt-in: only starts under the `cdm` profile, so the heavy amd64 image stays
# out of the default dev `up`:
# docker compose --profile cdm up -d
# Conn from the host: postgres://postgres:${POSTGRES_PASSWORD:-mypass}@localhost:5444/postgres
#
# NOTE (not yet wired to hades): hades' generated R runner connects to TREX's
# OWN pgwire (r_script.rs: server="localhost/trex"), not to this container, so
# pointing a Strategus run at `demo_cdm` requires the CDM to be reachable from
# inside trex's engine (e.g. attach/copy into trexdb, or teach the runner an
# alternate connection). This service makes the CDM data available; bridging it
# into trex is a follow-up tracked in the Phase 5 e2e task.
broadsea-atlasdb:
container_name: sibyl-atlasdb
image: ohdsi/broadsea-atlasdb:2.3.0
# amd64-only image (no arm64 variant) — keep the platform pin so it runs
# under emulation on arm64 hosts. The rest of the stack is now native.
platform: linux/amd64
profiles: ["cdm"]
mem_limit: "2G"
command: ["postgres", "-c", "wal_level=logical"]
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mypass}
ports:
# Host 5444: avoids the local pg (5432) and trex pgwire (5443) mappings.
- "5444:5432"
volumes:
- atlasdb-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 10
secrets:
# GitHub Packages token for @ohdsi/atlas-ui, taken from the NODE_AUTH_TOKEN env
# var at build time (a GitHub PAT with read:packages). Export it before
# `docker compose build trex-data`. Used by the web-builder stage in Dockerfile.
ghtoken:
environment: NODE_AUTH_TOKEN