-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
293 lines (277 loc) · 10.4 KB
/
Copy pathdocker-compose.yml
File metadata and controls
293 lines (277 loc) · 10.4 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
# One-command local stack - no external accounts (no Clerk, no AWS):
#
# docker compose up --build
#
# Then open http://localhost:3000/dashboard (no sign-in; fixed dev user).
# The `bootstrap` job prints copy-paste MCP / Claude / ChatGPT snippets:
# docker compose logs bootstrap
#
# Everything is configured through environment variables with local defaults;
# copy .env.docker.example to .env to override. Ports are published on
# 127.0.0.1 only: AUTH_PROVIDER=dev has no sign-in and must never be reachable
# from other machines.
#
# Profiles: tools Adminer DB UI http://localhost:8081
# pgbouncer transaction pooling in front of db; point the app at it:
# npm run docker:up:pgbouncer
# bot Slack/Discord chat surface (apps/bot)
# observability OpenTelemetry Collector + Jaeger (traces,
# http://localhost:16686) + Prometheus (metrics,
# http://localhost:9090). Point the apps at the collector:
# npm run docker:up:observability
name: pointup
x-dev-auth: &dev-auth
AUTH_PROVIDER: ${AUTH_PROVIDER:-dev}
DEV_USER_ID: ${DEV_USER_ID:-dev-user}
# Needed because the images run with NODE_ENV=production. The stack is safe
# only because every published port is bound to 127.0.0.1 (see `ports`).
ALLOW_INSECURE_DEV_AUTH: ${ALLOW_INSECURE_DEV_AUTH:-true}
DEV_AUTH_HOST_IS_LOOPBACK_ONLY: ${DEV_AUTH_HOST_IS_LOOPBACK_ONLY:-true}
x-db-direct: &db-direct postgresql://postgres:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-app}
services:
db:
image: postgres:17.6-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
POSTGRES_DB: ${POSTGRES_DB:-app}
ports:
- "127.0.0.1:${DB_PORT:-5432}:5432"
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d ${POSTGRES_DB:-app}"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
# Transaction-mode pooler (profile `pgbouncer`). With APP_DATABASE_URL pointed
# at it, web/worker exercise the prepare:false path of createDb(). Migrations
# always use the direct db URL (they need a session-level advisory lock).
pgbouncer:
image: edoburu/pgbouncer:v1.24.1-p1
profiles: ["pgbouncer"]
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
DB_PORT: "5432"
DB_USER: postgres
DB_PASSWORD: ${POSTGRES_PASSWORD:-password}
DB_NAME: ${POSTGRES_DB:-app}
POOL_MODE: transaction
AUTH_TYPE: scram-sha-256
MAX_CLIENT_CONN: "200"
DEFAULT_POOL_SIZE: "10"
LISTEN_PORT: "6432"
ports:
- "127.0.0.1:${PGBOUNCER_PORT:-6432}:6432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 6432 -U postgres"]
interval: 5s
timeout: 3s
retries: 10
# One-shot: migrate, seed the dev user's demo portfolio, mint the dev token
# and print connection snippets. Idempotent; exits 0.
bootstrap:
build:
context: .
dockerfile: Dockerfile.worker
command: ["bootstrap"]
restart: "no"
depends_on:
db:
condition: service_healthy
environment:
<<: *dev-auth
DATABASE_URL: *db-direct
POINTUP_DEV_TOKEN: ${POINTUP_DEV_TOKEN:-pu_dev_local_only_0123456789abcdef0123456789abcdef}
PUBLIC_WEB_URL: http://localhost:${WEB_PORT:-3000}
PUBLIC_MCP_URL: http://localhost:${MCP_PORT:-8787}/mcp
web:
build:
context: .
args:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:-}
restart: unless-stopped
depends_on:
bootstrap:
condition: service_completed_successfully
environment:
ASSISTANT_RUNTIME: ${ASSISTANT_RUNTIME:-legacy}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
# Server-only approved OAuth client; disabled with default dev auth/empty config.
CHATGPT_CLIENT_ID: ${CHATGPT_CLIENT_ID:-}
CHATGPT_REDIRECT_URI: ${CHATGPT_REDIRECT_URI:-}
CHATGPT_CLIENT_AUTH_METHOD: ${CHATGPT_CLIENT_AUTH_METHOD:-}
CHATGPT_CLIENT_SECRET: ${CHATGPT_CLIENT_SECRET:-}
ASSISTANT_MODEL: ${ASSISTANT_MODEL:-}
ASSISTANT_TRACING_ENABLED: ${ASSISTANT_TRACING_ENABLED:-false}
ASSISTANT_TIMEOUT_MS: ${ASSISTANT_TIMEOUT_MS:-30000}
ASSISTANT_MAX_TURNS: ${ASSISTANT_MAX_TURNS:-5}
OPENAI_AGENTS_DISABLE_TRACING: ${OPENAI_AGENTS_DISABLE_TRACING:-0}
<<: *dev-auth
DATABASE_URL: ${APP_DATABASE_URL:-postgresql://postgres:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-app}}
CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:-}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:-}
NEXT_PUBLIC_MCP_URL: http://localhost:${MCP_PORT:-8787}/mcp
APP_URL: ${APP_URL:-http://localhost:${WEB_PORT:-3000}}
LLM_PROVIDER: ${LLM_PROVIDER:-}
LLM_API_KEY: ${LLM_API_KEY:-}
BEDROCK_MODEL_ID: ${BEDROCK_MODEL_ID:-}
# Telemetry (docs/observability.md). Empty = no OpenTelemetry SDK, zero overhead.
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: pointup-web
LOG_LEVEL: ${LOG_LEVEL:-info}
# /metrics (Prometheus text) is off unless both are set.
METRICS_ENABLED: ${METRICS_ENABLED:-false}
METRICS_TOKEN: ${METRICS_TOKEN:-}
ports:
- "127.0.0.1:${WEB_PORT:-3000}:3000"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/api/health >/dev/null || exit 1"]
interval: 5s
timeout: 3s
retries: 30
start_period: 10s
# Remote MCP server (streamable HTTP): http://localhost:8787/mcp
# Clients send `Authorization: Bearer pu_...` (the dev token is in .env.docker.example).
mcp:
build:
context: .
dockerfile: Dockerfile.mcp
restart: unless-stopped
depends_on:
web:
condition: service_healthy
environment:
POINTUP_URL: http://web:3000
# Only this private upstream may carry MCP bearer tokens over HTTP.
POINTUP_TRUSTED_HTTP_ORIGIN: http://web:3000
HOST: 0.0.0.0
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
OTEL_SERVICE_NAME: pointup-mcp
LOG_LEVEL: ${LOG_LEVEL:-info}
ports:
- "127.0.0.1:${MCP_PORT:-8787}:8787"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8787/healthz >/dev/null || exit 1"]
interval: 5s
timeout: 3s
retries: 20
# Background jobs. Runs the in-process scheduler (`loop`: balance sync every
# 6h, weekly digest). Run any job on demand instead:
# docker compose run --rm worker sync | digest | alerts | watch
worker:
build:
context: .
dockerfile: Dockerfile.worker
command: ["loop"]
restart: unless-stopped
stop_grace_period: 30s
depends_on:
bootstrap:
condition: service_completed_successfully
mailpit:
condition: service_started
environment:
<<: *dev-auth
DATABASE_URL: ${APP_DATABASE_URL:-postgresql://postgres:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-app}}
MAILER: smtp
SMTP_URL: smtp://mailpit:1025
DIGEST_FROM_EMAIL: digest@pointup.local
# Without Clerk, digests go to this address, visible in Mailpit.
DIGEST_RECIPIENT_OVERRIDE: dev@pointup.local
CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:-}
WORKER_SYNC_INTERVAL_MINUTES: ${WORKER_SYNC_INTERVAL_MINUTES:-360}
WORKER_DIGEST_INTERVAL_MINUTES: ${WORKER_DIGEST_INTERVAL_MINUTES:-10080}
SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL:-}
DISCORD_WEBHOOK_URL: ${DISCORD_WEBHOOK_URL:-}
# Email testing UI: http://localhost:8025 (SMTP on 1025). Digests land here.
mailpit:
image: axllent/mailpit:v1.27
restart: unless-stopped
ports:
- "127.0.0.1:8025:8025"
- "127.0.0.1:1025:1025"
healthcheck:
test: ["CMD", "/mailpit", "readyz"]
interval: 10s
timeout: 3s
retries: 5
# Open-source database admin UI: http://localhost:8081
# (server: db, user: postgres, password: password, database: app)
adminer:
image: adminer:5
profiles: ["tools"]
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8081:8080"
# PointBot chat surface (Slack slash commands): http://localhost:8080
# docker compose --profile bot up bot
bot:
build:
context: .
dockerfile: Dockerfile.bot
profiles: ["bot"]
restart: unless-stopped
depends_on:
bootstrap:
condition: service_completed_successfully
environment:
DATABASE_URL: ${APP_DATABASE_URL:-postgresql://postgres:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-app}}
SLACK_SIGNING_SECRET: ${SLACK_SIGNING_SECRET:-}
DISCORD_PUBLIC_KEY: ${DISCORD_PUBLIC_KEY:-}
DISCORD_APP_ID: ${DISCORD_APP_ID:-}
BOT_DEFAULT_USER_ID: ${BOT_DEFAULT_USER_ID:-${DEV_USER_ID:-dev-user}}
# Assistant provider (same vars as the web app); omit for heuristic replies.
LLM_PROVIDER: ${LLM_PROVIDER:-}
LLM_API_KEY: ${LLM_API_KEY:-}
BEDROCK_MODEL_ID: ${BEDROCK_MODEL_ID:-}
ports:
- "127.0.0.1:8080:8080"
# ─── Observability (profile `observability`) ────────────────────────────
# Apps push OTLP to the collector (http://otel-collector:4318); it forwards
# traces to Jaeger and exposes metrics for Prometheus.
otel-collector:
image: otel/opentelemetry-collector-contrib:0.120.0
profiles: ["observability"]
restart: unless-stopped
command: ["--config=/etc/otelcol-contrib/config.yaml"]
volumes:
- ./deploy/observability/otel-collector.yaml:/etc/otelcol-contrib/config.yaml:ro
depends_on:
- jaeger
ports:
- "127.0.0.1:4318:4318"
# Trace UI: http://localhost:16686
jaeger:
image: jaegertracing/all-in-one:1.65.0
profiles: ["observability"]
restart: unless-stopped
environment:
COLLECTOR_OTLP_ENABLED: "true"
ports:
- "127.0.0.1:16686:16686"
# Metrics UI: http://localhost:9090
prometheus:
image: prom/prometheus:v3.2.1
profiles: ["observability"]
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=2d
volumes:
- ./deploy/observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro
depends_on:
- otel-collector
ports:
- "127.0.0.1:9090:9090"
volumes:
db-data: