Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
464 lines (448 loc) · 24.8 KB
/
Copy pathdocker-compose.yml
File metadata and controls
464 lines (448 loc) · 24.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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# STAR Internal Tools — the apps-machine stack.
#
# This is the reverse proxy (Caddy) + a verify-only auth + every app + an
# optional Cloudflare Tunnel. In the current split deployment it runs on the
# apps machine; the EC2 box runs OpenProject + the *login* auth separately (see
# deploy/ec2/). The full apps-machine runbook is deploy/apps/README.md.
#
# Auth split: login (Google OAuth, cookie minting) is centralized on the EC2
# auth at auth.starberkeley.org. The auth here runs with AUTH_VERIFY_ONLY=true
# and only validates cookies -- one minter, many verifiers. It shares JWT_SECRET
# with EC2 and sends logins to AUTH_PUBLIC_URL.
#
# Lives at the repo root on purpose: Compose resolves relative paths against the
# directory holding this file, so from deploy/ every `./auth`, `./landing` etc.
# would have pointed at deploy/auth, deploy/landing.
#
# Usage (apps machine, behind a tunnel):
# cp .env.example .env # then fill it in (SCHEME=http, AUTH_VERIFY_ONLY=true, …)
# SCHEME=http docker compose --profile tunnel up -d --build
# (drop the published 80/443 below -- cloudflared is the only ingress)
#
# Let's Encrypt instead of a tunnel (single public machine): leave SCHEME=https
# and `docker compose up -d --build`.
#
# Image pinning: every ghcr.io/calstar/star-* image is tagged ${STAR_IMAGE_TAG:-latest}.
# Unset (the default) tracks :latest — the newest push to main. To pin the whole
# stack to a specific build, set STAR_IMAGE_TAG=sha-<short> in .env (CI publishes a
# sha-<short> tag alongside :latest on every push). Roll back = change that one line
# and `docker compose up -d`. See deploy/apps/README.md.
services:
# ── Reverse proxy + TLS + auth gate ──────────────────────────────────────
caddy:
image: caddy:2-alpine
restart: unless-stopped
mem_limit: 512m
memswap_limit: 512m # == mem_limit -> no swap; OOM this container alone
# --watch reloads the config in place when deploy/caddy/Caddyfile changes, so
# a `git pull` on the box takes effect with no manual reload. The config dir
# (not the single file) is mounted below: a single-file bind mount pins to the
# file's inode, and git rewrites the file to a new inode on pull, so the
# container would keep serving the stale config until recreated. Mounting the
# dir means the container always reads the current Caddyfile.
command: caddy run --config /etc/caddy/Caddyfile --adapter caddyfile --watch
ports:
- "80:80"
- "443:443"
environment:
SCHEME: ${SCHEME:-https}
BASE_DOMAIN: ${BASE_DOMAIN:-starberkeley.org}
ACME_EMAIL: ${ACME_EMAIL:-infra@starberkeley.org}
# DAQ is not a container -- see the daq-server block in the Caddyfile.
DAQ_BACKEND: ${DAQ_BACKEND:-host.docker.internal:8081}
DAQ_GUI: ${DAQ_GUI:-host.docker.internal:3000}
extra_hosts:
# Lets Caddy reach the DAQ processes running natively on the host.
# Docker Desktop provides this name already; on Linux it must be mapped.
- "host.docker.internal:host-gateway"
volumes:
- ./deploy/caddy:/etc/caddy:ro # mount the dir, not the file (see command above)
- caddy_data:/data # TLS certificates (persisted across restarts)
- caddy_config:/config
depends_on:
- auth
- engine-design-api
- engine-design-frontend
- pid-designer-api
- pid-designer-frontend
- feed-twin-api
- feed-twin-frontend
- star-openrocket-api
- star-openrocket-frontend
- parts-hub
- daq-viewer
- landing
# ── Cloudflare Tunnel (optional ingress) ─────────────────────────────────
# Opt in with `--profile tunnel`. Cloudflare terminates TLS at the edge, so
# set SCHEME=http to stop Caddy from trying to issue its own certificates.
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
mem_limit: 256m
memswap_limit: 256m # == mem_limit -> no swap; OOM this container alone
profiles: ["tunnel"]
command: tunnel --no-autoupdate run
environment:
# Same reasoning as JWT_SECRET below: a required-variable guard would
# break every other compose command. cloudflared exits with a clear
# error of its own when the token is empty.
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:-}
extra_hosts:
# SSH access to the *host* box over the same tunnel. In the Zero Trust
# dashboard, "Add published application" on this tunnel (e.g.
# ssh-rfs.starberkeley.org, Service URL ssh://host.docker.internal:22 -- the
# new UI has no type dropdown, the ssh:// scheme is the type), and
# cloudflared proxies it to
# the host's sshd. `host-gateway` is what lets the container reach the
# host's port 22 (Docker Desktop provides the name already; Linux needs
# this mapping). Client side: `ssh -o ProxyCommand="cloudflared access ssh
# --hostname ssh-rfs.starberkeley.org" user@ssh-rfs.starberkeley.org`.
- "host.docker.internal:host-gateway"
depends_on:
- caddy
# ── Auth service ─────────────────────────────────────────────────────────
auth:
image: ghcr.io/calstar/star-auth:${STAR_IMAGE_TAG:-latest}
build:
context: ./auth
restart: unless-stopped
mem_limit: 512m
memswap_limit: 512m # == mem_limit -> no swap; OOM this container alone
env_file:
# Optional so the stack can also be driven from the real environment
# (systemd EnvironmentFile, a secret manager) instead of a file in the
# repo. If neither supplies the values, auth fails loudly at startup.
- path: ./auth/.env # copy from auth/.env.example and fill in
required: false
environment:
BASE_DOMAIN: ${BASE_DOMAIN:-starberkeley.org}
AUTH_PUBLIC_URL: ${AUTH_PUBLIC_URL:-https://auth.starberkeley.org}
# On the apps machine set AUTH_VERIFY_ONLY=true: this auth only validates
# cookies (login lives on the EC2 box), so it needs just JWT_SECRET and no
# Google secret. See deploy/apps/. Default false = full login node.
AUTH_VERIFY_ONLY: ${AUTH_VERIFY_ONLY:-false}
# Single source for the shared secret (root .env), so a verify-only node
# needs no auth/.env at all. On a login node, keep the Google/Flask secrets
# in auth/.env; JWT_SECRET here still wins, so set it in root .env.
JWT_SECRET: ${JWT_SECRET:-}
# Read the onshape allowlist from the bind-mounted config dir below, not
# the copy baked into the image, so it can be edited live on the server.
ONSHAPE_ALLOWLIST_FILE: /config/onshape_allowlist.txt
volumes:
# Editable-on-the-server allowlists. allowlist.py re-reads on mtime change,
# so edits here take effect on the user's next request -- no restart, no
# rebuild. Mount the *directory* (not the single file): an editor that
# saves via rename swaps the inode, which a single-file bind mount would
# not follow and the mtime watch would never see.
- ./auth/allowlists:/config:ro
# ── Engine Design ────────────────────────────────────────────────────────
# Built from the repo root: the image copies several EngineDesign/ subtrees
# plus lib/stardesign. Auth is Caddy's job, so no JWT_SECRET here.
engine-design-api:
image: ghcr.io/calstar/star-engine-design-api:${STAR_IMAGE_TAG:-latest}
build:
context: .
dockerfile: EngineDesign/Dockerfile.api
restart: unless-stopped
mem_limit: 2g
memswap_limit: 2g # == mem_limit -> no swap; OOM this container alone
environment:
# Never build a CEA cache in-process on the server: a cold build is ~39k
# rocketcea calls (hours) whose growing in-process cache took the host to
# >12 GB. The committed output/cache/*.npz tables are what we load; a
# missing/stale one must fail fast here, not trigger a build. Local dev
# leaves this unset and can still build on demand. Regenerate offline via
# .github/workflows/regenerate-cea-cache.yml.
ED_ALLOW_CEA_BUILD: "0"
# Layer X's "From pid-designer" lists and fetches drawings from the
# pid-designer API over the compose network, forwarding the caller's
# X-Auth-Email. Unset, it falls back to 127.0.0.1:8001 -- inside this
# container that is the engine-design container itself, so the drawing
# picker reported "pid-designer is not answering". feed-twin-api already
# points here the same way.
PID_DESIGNER_URL: http://pid-designer-api:8001
# Per-user saved configs + working copies live here (a mounted volume).
# See userdata.py.
USERDATA_DIR: /data
# Who a design can be shared with. The tool asks auth for the roster of
# everyone who has signed in. Only the EC2 login node keeps one (the auth
# container on this machine is verify-only and never sees a /callback),
# so this deliberately points across at the public URL, not at auth:5000.
# The backend forwards the caller's own session cookie and nothing else,
# to this URL and never one from the request -- see
# EngineDesign/backend/directory.py. Unset is fine: the roster then
# falls back to whoever already has data on the shared volume.
AUTH_USERS_URL: ${AUTH_USERS_URL:-${AUTH_PUBLIC_URL:-https://auth.starberkeley.org}}
# Design version history (microversions + releases) in S3. Empty bucket ->
# history falls back to the local volume (dev/no-AWS). IAM access keys, not
# an instance role (the apps machine is not on EC2).
ENGINE_S3_BUCKET: ${ENGINE_S3_BUCKET:-}
ENGINE_S3_PREFIX: ${ENGINE_S3_PREFIX:-engine}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
volumes:
- userdata:/data
engine-design-frontend:
image: ghcr.io/calstar/star-engine-design-frontend:${STAR_IMAGE_TAG:-latest}
build:
# Repo root, not the subproject: the app compiles lib/stardesign-ui,
# the design-tool UI shared by all three frontends.
context: .
dockerfile: EngineDesign/frontend/Dockerfile
restart: unless-stopped
mem_limit: 512m
memswap_limit: 512m # == mem_limit -> no swap; OOM this container alone
# ── P&ID Designer ────────────────────────────────────────────────────────
# Auth is Caddy's job; the image is self-contained, so it builds from the
# subproject dir.
pid-designer-api:
image: ghcr.io/calstar/star-pid-designer-api:${STAR_IMAGE_TAG:-latest}
build:
# Repo root, not the subproject: the backend is built on lib/stardesign,
# the design core shared by all three design tools.
context: .
dockerfile: pid-designer/Dockerfile.api
restart: unless-stopped
mem_limit: 2g
memswap_limit: 2g # == mem_limit -> no swap; OOM this container alone
environment:
# Per-user working copies (autosave target) live on this volume, keyed by
# X-Auth-Email. See pid-designer/backend/userdata.py.
USERDATA_DIR: /data
# Who a design can be shared with. The tool asks auth for the roster of
# everyone who has signed in. Only the EC2 login node keeps one (the auth
# container on this machine is verify-only and never sees a /callback),
# so this deliberately points across at the public URL, not at auth:5000.
# The backend forwards the caller's own session cookie and nothing else,
# to this URL and never one from the request -- see
# pid-designer/backend/directory.py. Unset is fine: the roster then
# falls back to whoever already has data on the shared volume.
AUTH_USERS_URL: ${AUTH_USERS_URL:-${AUTH_PUBLIC_URL:-https://auth.starberkeley.org}}
# Diagram version history (microversions + releases) in S3. Empty bucket ->
# history falls back to the local volume (dev/no-AWS). The apps machine is
# not on EC2, so these are IAM access keys, not an instance role.
PID_S3_BUCKET: ${PID_S3_BUCKET:-}
PID_S3_PREFIX: ${PID_S3_PREFIX:-pid}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
volumes:
- userdata:/data
pid-designer-frontend:
image: ghcr.io/calstar/star-pid-designer-frontend:${STAR_IMAGE_TAG:-latest}
build:
# Repo root, not the subproject: the app compiles lib/stardesign-ui,
# the design-tool UI shared by all three frontends.
context: .
dockerfile: pid-designer/frontend/Dockerfile
restart: unless-stopped
mem_limit: 512m
memswap_limit: 512m # == mem_limit -> no swap; OOM this container alone
# ── Feed Twin ────────────────────────────────────────────────────────────
# The digital twin of the feed system. Imports a drawing out of pid-designer
# and an engine out of EngineDesign and solves the two together, so it is the
# one app here that talks to its siblings rather than only to the browser.
feed-twin-api:
image: ghcr.io/calstar/star-feed-twin-api:${STAR_IMAGE_TAG:-latest}
build:
# Repo root, not the subproject: the backend is built on lib/feedtwin,
# the physics core it shares with EngineDesign.
context: .
dockerfile: feed-twin/Dockerfile.api
restart: unless-stopped
# Larger than the other design tools on purpose: CoolProp's bicubic tables
# are built per species on first use and held for the life of the process.
mem_limit: 3g
memswap_limit: 3g # == mem_limit -> no swap; OOM this container alone
environment:
# Imported drawings and engine configs, addressed by the SHA-256 of their
# own bytes. On the volume rather than in the image so a redeploy does not
# drop what people have imported -- and so a result from last month can
# still name the exact artifact it ran on.
FEEDTWIN_LIBRARY: /data/feed-twin/library
# Stands (and the runs made on them): per-user documents on the same
# volume, under <user>/stand -- shared, checked out and versioned like
# pid-designer's diagrams. See feed-twin/backend/routers/stands.py.
USERDATA_DIR: /data
AUTH_USERS_URL: ${AUTH_USERS_URL:-${AUTH_PUBLIC_URL:-https://auth.starberkeley.org}}
FEEDTWIN_S3_BUCKET: ${FEEDTWIN_S3_BUCKET:-}
FEEDTWIN_S3_PREFIX: ${FEEDTWIN_S3_PREFIX:-stand}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
# Where the siblings live inside the compose network. This is the whole
# integration: with these set, importing a P&ID is picking one out of a
# list rather than exporting a file and uploading it. The caller's
# X-Auth-Email is forwarded on each request, so the design tool applies
# its own sharing rules -- see feed-twin/backend/designtools.py.
PID_DESIGNER_URL: http://pid-designer-api:8001
ENGINE_DESIGN_URL: http://engine-design-api:8000
volumes:
- userdata:/data
feed-twin-frontend:
image: ghcr.io/calstar/star-feed-twin-frontend:${STAR_IMAGE_TAG:-latest}
build:
context: .
dockerfile: feed-twin/frontend/Dockerfile
restart: unless-stopped
mem_limit: 512m
memswap_limit: 512m # == mem_limit -> no swap; OOM this container alone
# ── STAR OpenRocket ──────────────────────────────────────────────────────
# CAD stability + ascent flight dynamics + recovery (the recovery calculator
# merged in as a tab). No in-app auth: Caddy is the gate, and it brokers the
# Onshape key pair server-side.
star-openrocket-api:
image: ghcr.io/calstar/star-openrocket-api:${STAR_IMAGE_TAG:-latest}
build:
# Repo root, not the subproject: the backend is built on lib/stardesign,
# the design core shared by all three design tools.
context: .
dockerfile: star-openrocket/Dockerfile.api
restart: unless-stopped
mem_limit: 2g
memswap_limit: 2g # == mem_limit -> no swap; OOM this container alone
environment:
# Onshape API key pair. Lives in the shared root .env like every other
# service's secrets (JWT_SECRET, AWS_*) -- one env file for the whole
# stack. Empty -> the stack still comes up and the cache-first endpoints
# work; only live Onshape calls (refresh/build) fail until it's filled in.
# Get a pair at https://dev-portal.onshape.com.
ONSHAPE_ACCESS_KEY: ${ONSHAPE_ACCESS_KEY:-}
ONSHAPE_SECRET_KEY: ${ONSHAPE_SECRET_KEY:-}
# Optional: only to target a non-default Onshape instance / api version.
ONSHAPE_BASE_URL: ${ONSHAPE_BASE_URL:-}
# Per-user saved designs, units and configs live here (a mounted volume).
USERDATA_DIR: /data
# Who a design can be shared with. The tool asks auth for the roster of
# everyone who has signed in. Only the EC2 login node keeps one (the auth
# container on this machine is verify-only and never sees a /callback),
# so this deliberately points across at the public URL, not at auth:5000.
# The backend forwards the caller's own session cookie and nothing else,
# to this URL and never one from the request -- see
# star-openrocket/backend/directory.py. Unset is fine: the roster then
# falls back to whoever already has data on the shared volume.
AUTH_USERS_URL: ${AUTH_USERS_URL:-${AUTH_PUBLIC_URL:-https://auth.starberkeley.org}}
# Design version history (microversions + releases) in S3. Empty bucket ->
# history falls back to the local volume (dev/no-AWS).
OPENROCKET_S3_BUCKET: ${OPENROCKET_S3_BUCKET:-}
OPENROCKET_S3_PREFIX: ${OPENROCKET_S3_PREFIX:-openrocket}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
volumes:
# Built-model cache: expensive Onshape builds (manifest + glb) survive a
# rebuild. CACHE_ROOT in backend/main.py is /app/cache.
- onshape_cache:/app/cache
# Per-user designs / configs (userdata.py writes under USERDATA_DIR).
- userdata:/data
star-openrocket-frontend:
image: ghcr.io/calstar/star-openrocket-frontend:${STAR_IMAGE_TAG:-latest}
build:
# Repo root, not the subproject: the app compiles lib/stardesign-ui,
# the design-tool UI shared by all three frontends.
context: .
dockerfile: star-openrocket/frontend/Dockerfile
restart: unless-stopped
mem_limit: 512m
memswap_limit: 512m # == mem_limit -> no swap; OOM this container alone
# ── STAR Parts Hub ───────────────────────────────────────────────────────
# The team's CAD parts library, plus the "STAR Parts" panel inside Onshape
# assemblies. One container: Node serves the hub UI, its API and the panel.
# The hub sits behind the usual gate; /panel/* deliberately does not (it runs
# inside Onshape's iframe and authenticates with Onshape OAuth) -- see the
# Caddyfile. Setup runbook: parts-hub/DEPLOY.md.
parts-hub:
image: ghcr.io/calstar/star-parts-hub:${STAR_IMAGE_TAG:-latest}
build:
context: ./parts-hub
dockerfile: Dockerfile
restart: unless-stopped
# Drawing thumbnails of big STEP files briefly needs a lot of memory.
mem_limit: 2g
memswap_limit: 2g # == mem_limit -> no swap; OOM this container alone
environment:
# Always https: Cloudflare terminates TLS even when Caddy runs SCHEME=http.
PUBLIC_BASE_URL: https://parts.${BASE_DOMAIN:-starberkeley.org}
OAUTH_REDIRECT_URL: https://parts.${BASE_DOMAIN:-starberkeley.org}/panel/oauth/callback
# Read + Write key pair: the hub imports parts and creates versions in the
# library document. A read-only pair fails with "Invalid API key state".
ONSHAPE_ACCESS_KEY: ${PARTS_ONSHAPE_ACCESS_KEY:-${ONSHAPE_ACCESS_KEY:-}}
ONSHAPE_SECRET_KEY: ${PARTS_ONSHAPE_SECRET_KEY:-${ONSHAPE_SECRET_KEY:-}}
# The "STAR Parts" OAuth app (Onshape Enterprise settings -> Developer).
ONSHAPE_CLIENT_ID: ${PARTS_ONSHAPE_CLIENT_ID:-}
ONSHAPE_CLIENT_SECRET: ${PARTS_ONSHAPE_CLIENT_SECRET:-}
LIBRARY_DOCUMENT_ID: ${PARTS_LIBRARY_DOCUMENT_ID:-}
# Same reasoning as JWT_SECRET: no required-variable guard here, it would
# break every other compose command. The app exits with a clear list of
# what is missing instead.
SESSION_SECRET: ${PARTS_SESSION_SECRET:-}
volumes:
# SQLite + original CAD files + thumbnails. Back this up.
- parts_data:/data
# ── DAQ Run Viewer ───────────────────────────────────────────────────────
# Read-only web viewer for PAST Elodin runs (browse, plot, export CSV). One
# container: FastAPI serves both /api and the built SPA on 8420. It reads the
# run DBs straight off the host (bind-mounted read-only) and shells out to the
# elodin-db binary baked into the image to export them to parquet. This is a
# separate concern from the DAQ *server* below, which stays on the host.
daq-viewer:
image: ghcr.io/calstar/star-daq-viewer:${STAR_IMAGE_TAG:-latest}
build:
context: ./daq-server/tools/postprocessing/webviewer
dockerfile: Dockerfile
restart: unless-stopped
mem_limit: 1g
memswap_limit: 1g # == mem_limit -> no swap; OOM this container alone
# elodin-db is built on io_uring (stellarator), and Docker's default seccomp
# profile blocks the io_uring_* syscalls -- without this the binary panics at
# startup (io_uring_setup -> EPERM), so every export would fail. This is the
# one app that needs it; it's an internal, login-gated, read-only tool.
security_opt:
- seccomp=unconfined
environment:
ELODIN_DB_DIR: /data/elodin
WEBVIEWER_CACHE_DIR: /cache
# Pace CSV downloads so a multi-GB export can't saturate the box uplink.
WEBVIEWER_MAX_DOWNLOAD_BPS: ${WEBVIEWER_MAX_DOWNLOAD_BPS:-10485760} # 10 MB/s
volumes:
# The host's Elodin run-DB directory. Point ELODIN_DB_HOST_DIR at the real
# path on the box (see .env.example) -- a wrong path silently bind-mounts an
# empty dir and the viewer shows zero runs. NOT read-only: elodin-db mmaps
# each DB O_RDWR even just to *export* it, so a :ro mount fails with
# "Read-only file system". The viewer's own code never writes here (only to
# /cache), and past-run dirs are immutable, so rw is safe.
- ${ELODIN_DB_HOST_DIR:-/home/aidan/.local/share/elodin}:/data/elodin
# Writable parquet export cache (immutable past runs -> never invalidated).
- daq_viewer_cache:/cache
# ── DAQ Server ───────────────────────────────────────────────────────────
# Intentionally absent. The C++ bridge, elodin-db and the Node backend need
# the test-stand hardware, so they run natively under systemd -- see
# daq-server/deploy/systemd/. Caddy proxies to them on the host; the routes
# are in deploy/caddy/Caddyfile.
# ── Landing page ─────────────────────────────────────────────────────────
landing:
image: ghcr.io/calstar/star-landing:${STAR_IMAGE_TAG:-latest}
build:
context: ./landing
dockerfile: Dockerfile
restart: unless-stopped
mem_limit: 256m
memswap_limit: 256m # == mem_limit -> no swap; OOM this container alone
volumes:
caddy_data:
caddy_config:
onshape_cache:
# Parquet export cache for the DAQ Run Viewer. Immutable past runs, so this is
# a pure cache -- safe to wipe; it just re-exports on next view.
daq_viewer_cache:
# Per-user data (engine-design + star-openrocket + pid-designer): named
# configs, recovery unit prefs, and P&ID working copies. One shared volume,
# which is what makes cross-user "browse + copy" and sharing a directory walk
# rather than a database -- each app lists its siblings under <user>/<app>/.
# Keyed by X-Auth-Email inside. See */backend/userdata.py and the sharing
# section of */backend/routers/documents.py.
userdata:
# STAR Parts Hub: its SQLite database, the uploaded CAD files and thumbnails.
# Not a cache -- back it up (parts-hub/README.md, "Data and backups").
parts_data: