-
Notifications
You must be signed in to change notification settings - Fork 54
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
166 lines (159 loc) · 8.07 KB
/
Copy pathdocker-compose.yml
File metadata and controls
166 lines (159 loc) · 8.07 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
# Self-host / prod stack. Boot the core with: docker compose --profile full up --build
# Convert a model (job): docker compose run --rm converter /data/model.ifc /data/model.frag
#
# Resource limits (opt-in): every service below has a commented-out `deploy.resources.limits`
# block keyed off env vars (AEC_*_MEM_LIMIT / AEC_*_CPUS — see .env.example for sizing guidance).
# They are OFF by default on purpose: a hard memory cap that is too low will OOM-kill a legitimate
# large-IFC conversion mid-run. Uncomment and set values sized for YOUR host before enabling.
# `deploy.resources.limits` is honored by `docker compose up` (v2); no Swarm required.
services:
postgres:
image: postgres:17
environment:
POSTGRES_USER: "${POSTGRES_USER:-bim}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-bim}"
POSTGRES_DB: "${POSTGRES_DB:-bim}"
ports: ["5432:5432"]
volumes: ["postgres-data:/var/lib/postgresql/data"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bim"]
interval: 5s
timeout: 3s
retries: 10
# deploy: # opt-in cap — uncomment + size for your host
# resources:
# limits:
# memory: "${AEC_PG_MEM_LIMIT:-2g}"
minio:
# Pinned. An unpinned `minio/minio` resolves to :latest, so two developers -- or a developer and
# CI -- can silently run different object stores, and a breaking upstream release arrives without
# a commit. Dependabot's docker ecosystem keeps the pins fresh via PRs.
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: "${S3_ACCESS_KEY:-minioadmin}"
MINIO_ROOT_PASSWORD: "${S3_SECRET_KEY:-minioadmin}"
ports: ["9000:9000", "9001:9001"]
volumes: ["minio-data:/data"]
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 3s
retries: 10
# deploy: # opt-in cap — uncomment + size for your host
# resources:
# limits:
# memory: "${AEC_MINIO_MEM_LIMIT:-1g}"
api:
image: massing-api
build:
context: .
dockerfile: services/api/Dockerfile
depends_on:
postgres: { condition: service_healthy }
minio: { condition: service_healthy }
environment: &api-env
DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-bim}:${POSTGRES_PASSWORD:-bim}@postgres:5432/${POSTGRES_DB:-bim}"
S3_ENDPOINT: http://minio:9000
S3_ACCESS_KEY: "${S3_ACCESS_KEY:-minioadmin}"
S3_SECRET_KEY: "${S3_SECRET_KEY:-minioadmin}"
S3_BUCKET: "${S3_BUCKET:-aec-bim}"
AEC_RBAC: "${AEC_RBAC:-0}" # set 1 to enforce project roles
AEC_API_KEY: "${AEC_API_KEY:-}" # bearer token = admin (set in prod)
AEC_AUTH_SECRET: "${AEC_AUTH_SECRET:-}" # signs login tokens (falls back to AEC_API_KEY)
AEC_CORS_ORIGINS: "${AEC_CORS_ORIGINS:-http://localhost:5173}"
UVICORN_WORKERS: "${UVICORN_WORKERS:-4}" # multi-worker (Postgres) so a reconvert can't freeze the API
# JOB-WORKER-SPLIT: the heavy job kinds (COBie export, bundles, generative runs) are full-model
# IFC parses. Run inside this container they compete for CPU and memory with every request it is
# serving, so the compose stack runs them in the `worker` service below instead. Set this to
# `inline` to go back to one container doing both — the code default is inline precisely so a
# deployment that is NOT using this file is unaffected.
AEC_JOB_WORKER: "${AEC_JOB_WORKER:-off}"
APS_CLIENT_ID: "${APS_CLIENT_ID:-}" # optional paid Autodesk RVT bridge
APS_CLIENT_SECRET: "${APS_CLIENT_SECRET:-}"
ports: ["8000:8000"]
volumes: ["ifc-data:/app/ifc"] # uploaded source IFCs persist across restarts
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 5s
timeout: 3s
retries: 20
profiles: ["full"]
# deploy: # opt-in cap — uncomment + size for your host
# resources: # keep generous: UVICORN_WORKERS=4 + an in-process reconvert
# limits:
# memory: "${AEC_API_MEM_LIMIT:-4g}"
# cpus: "${AEC_API_CPUS:-2}"
# JOB-WORKER-SPLIT — the durable queue, in its own container. Same image and same environment as
# the API (the `&api-env` anchor above, so the two cannot drift apart), different command.
#
# Scale it independently of request serving, which is the entire point:
# docker compose --profile full up -d --scale worker=3
# That is safe without any further change because job claim is a compare-and-swap and `pid_lock`
# takes a Postgres advisory lock — two workers cannot both win one job, and a mutating job still
# serialises against a concurrent API edit on the same project.
#
# No ports and no healthcheck: it serves nothing, so there is nothing to probe. "Is the queue
# moving?" is a question about the oldest `queued` row, not about this container being up — a
# wedged worker and an idle one look identical from outside.
worker:
image: massing-api
build:
context: .
dockerfile: services/api/Dockerfile
depends_on:
postgres: { condition: service_healthy }
minio: { condition: service_healthy }
environment:
<<: *api-env
AEC_JOB_WORKER: "inline" # this process IS the worker; never `off` or it does nothing at all
command: ["python", "-m", "aec_api.worker"]
# The image bakes a HEALTHCHECK that curls localhost:8000/health. This service binds no port and
# serves nothing, so that probe can never pass and the container sits permanently `unhealthy` —
# which is worse than no signal, because it trains whoever is watching to ignore the column. The
# comment above claimed there was no healthcheck; there was, inherited from the image.
healthcheck: { disable: true }
volumes: ["ifc-data:/app/ifc"] # same uploaded-IFC volume as the API — jobs re-parse those files
profiles: ["full"]
# deploy: # opt-in cap — size for your host; this is where the heavy parses
# resources: # now live, so it wants the generous limit the API no longer needs
# limits:
# memory: "${AEC_WORKER_MEM_LIMIT:-4g}"
# cpus: "${AEC_WORKER_CPUS:-2}"
# one-shot demo seeder (api must be enabled, so pass both profiles):
# docker compose --profile full --profile seed run --rm seed
# DEV/TEST ONLY — never run against production. The script refuses if projects already exist
# (see docs/PRODUCTION_CHECKLIST.md); --force overrides only for labs.
seed:
image: massing-api
depends_on:
api: { condition: service_healthy }
command: ["python", "/app/services/api/seed_demo.py", "--url", "http://api:8000", "--user", "gc"]
profiles: ["seed"]
web:
build:
context: .
dockerfile: apps/web/Dockerfile
depends_on:
api: { condition: service_started }
ports: ["${WEB_PORT:-8080}:80"]
profiles: ["full"]
# deploy: # opt-in cap — uncomment + size for your host
# resources: # nginx serving static assets — modest footprint
# limits:
# memory: "${AEC_WEB_MEM_LIMIT:-256m}"
converter:
build:
context: .
dockerfile: services/converter/Dockerfile
volumes: ["./data:/data"] # drop IFCs here; .frag written here
profiles: ["tools"] # job-style: `docker compose run --rm converter ...`
# deploy: # opt-in cap — uncomment + size for your host
# resources: # IFC→Fragments is memory-hungry: size well above your
# limits: # largest model and against AEC_PUBLISH_WORKERS (PR #70),
# memory: "${AEC_CONVERTER_MEM_LIMIT:-8g}" # since parallel jobs each hold a model in RAM.
# cpus: "${AEC_CONVERTER_CPUS:-4}"
volumes:
postgres-data:
minio-data:
ifc-data: