-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
394 lines (381 loc) · 19 KB
/
Copy pathdocker-compose.yml
File metadata and controls
394 lines (381 loc) · 19 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
# BackAI local development stack
#
# Brings up: customer app, admin dashboard, runtime, Postgres, MinIO,
# AgentField control plane, LiteLLM, and webhook sidecars.
#
# Quickstart:
# cp .env.example .env
# # optional: set OPENROUTER_API_KEY for live model calls
# node scripts/preflight.mjs
# docker compose up
services:
postgres:
# pgvector-enabled Postgres so AgentField's vector memory works out of the
# box. Drop-in compatible with the official postgres image.
image: pgvector/pgvector:pg16
environment:
POSTGRES_USER: afstack
POSTGRES_PASSWORD: afstack
POSTGRES_MULTIPLE_DATABASES: afstack,agentfield
# Provision a restricted NOSUPERUSER NOBYPASSRLS serving role so the
# runtime enforces per-tenant RLS (the superuser POSTGRES_USER bypasses
# it). See scripts/postgres-init.sh. Dev-only default password.
AF_STACK_APP_DB_PASSWORD: ${AF_STACK_APP_DB_PASSWORD:-afstack_app}
ports:
- "${POSTGRES_PORT:-5432}:5432"
command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"]
volumes:
- postgres-data:/var/lib/postgresql/data
- ./deploy/postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
- ./scripts/postgres-init.sh:/docker-entrypoint-initdb.d/postgres-init.sh:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U afstack"]
interval: 5s
timeout: 3s
retries: 10
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${AF_STACK_S3_ACCESS_KEY:-minio}
MINIO_ROOT_PASSWORD: ${AF_STACK_S3_SECRET_KEY:-minio-secret}
ports:
- "${MINIO_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 5
# LiteLLM Proxy — handles every upstream LLM provider so the runtime
# doesn't ship a hand-rolled client per vendor. BackAI forwards
# /api/v1/llm/* to this sidecar; LiteLLM routes to OpenRouter / OpenAI /
# Anthropic / Google / Mistral / DeepSeek / Groq / Cohere / Bedrock / etc.
# based on apps/backend/litellm-config.yaml. BackAI keeps tenant
# resolution, cost ledger, budgets, cache, and hooks on its side.
litellm:
image: ghcr.io/berriai/litellm:main-stable
ports:
- "${LITELLM_PORT:-4000}:4000"
volumes:
- ./apps/backend/litellm-config.yaml:/app/config.yaml:ro
environment:
# Upstream provider keys. Only the ones you set actually get used —
# LiteLLM activates a model entry only when its required key is present.
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
# Internal sidecar auth. Shared with the runtime container only.
# Customers never see this key — they authenticate with their AF
# Stack tenant API key one layer earlier.
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY:-sk-litellm-dev}
command: ["--config", "/app/config.yaml", "--port", "4000"]
restart: unless-stopped
agentfield:
image: agentfield/control-plane:latest
environment:
# AgentField listens on 8080 inside the container by default.
# We map external 8081 -> internal 8080 so it doesn't conflict with
# the suite runtime which uses 8080 inside the container too.
AGENTFIELD_STORAGE_MODE: postgres
AGENTFIELD_STORAGE_POSTGRES_URL: postgres://afstack:afstack@postgres:5432/agentfield?sslmode=disable
AGENTFIELD_STORAGE_POSTGRES_ENABLE_MEMORY_FALLBACK: "false"
AGENTFIELD_STORAGE_POSTGRES_ENABLE_DID_FALLBACK: "false"
AGENTFIELD_STORAGE_POSTGRES_ENABLE_VC_FALLBACK: "false"
AGENTFIELD_STORAGE_POSTGRES_ENABLE_AUTO_MIGRATION: "true"
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ports:
- "${AGENTFIELD_PORT:-8081}:8080"
depends_on:
postgres:
condition: service_healthy
# No healthcheck: AF's container is alpine-based without wget/curl.
# We rely on `restart: unless-stopped` + the suite runtime's health
# probe instead.
restart: unless-stopped
# BackAI runtime (Go single binary).
runtime:
build:
context: .
dockerfile: services/runtime/Dockerfile
environment:
AF_STACK_HTTP_ADDR: ":8080"
AF_STACK_METRICS_ADDR: ":9090"
# Serve as the restricted role (real per-tenant RLS); migrate as the
# privileged owner (the restricted role has no DDL rights). The runtime
# refuses to start as an RLS-bypassing role once multi-tenancy is on.
AF_STACK_DATABASE_URL: postgres://afstack_app:${AF_STACK_APP_DB_PASSWORD:-afstack_app}@postgres:5432/afstack?sslmode=disable
AF_STACK_MIGRATE_DATABASE_URL: postgres://afstack:afstack@postgres:5432/afstack?sslmode=disable
AF_STACK_AGENTFIELD_URL: http://agentfield:8080
# Workload modules (PRD R2). The path matches the ro mount below;
# the enabled list is empty by default — the shipped notes module
# is a reference, opt in with AF_STACK_WORKLOAD_MODULES=notes.
WORKLOAD_MODULES_PATH: /app/workload-modules
AF_STACK_WORKLOAD_MODULES: ${AF_STACK_WORKLOAD_MODULES:-}
# Remote (Python/TS) job kinds served by external pull-workers (R3):
# comma list of <kind>[:<language>], e.g. "index-document:python".
AF_STACK_REMOTE_JOB_KINDS: ${AF_STACK_REMOTE_JOB_KINDS:-}
# KMS for the secrets vault (envelope encryption). Override in
# production with `openssl rand -hex 32`.
AF_STACK_AUTH_SECRET: ${AF_STACK_AUTH_SECRET:-dev-secret-change-me-in-prod}
AF_STACK_KMS_KEY: ${AF_STACK_KMS_KEY:-dev-secret-change-me}
# Deployment mode master switch: "saas" (default, multi-tenant with
# auth + billing governed by their module flags) or "personal" (a
# single-user app — auth and billing are forced OFF, so the app runs
# with no login and no paywall under the default tenant). Flip it with
# AF_STACK_MODE=personal (or run: af-stack mode personal) and restart.
AF_STACK_MODE: ${AF_STACK_MODE:-saas}
# Module enable flags. AF_STACK_MODULE_<UPPER_SNAKE>=true flips a
# module on regardless of what config.yaml says. Helpful for
# docker-compose where file mounts may not be readable by the
# distroless nonroot user. In personal mode these are overridden:
# multi-tenancy and billing are forced off regardless of these values.
AF_STACK_MODULE_MULTI_TENANCY: ${AF_STACK_MODULE_MULTI_TENANCY:-false}
AF_STACK_MODULE_BILLING: ${AF_STACK_MODULE_BILLING:-true}
# LLM gateway (Phase 7) — points at the LiteLLM sidecar. Customers
# call /api/v1/llm/* on this runtime; we forward to LiteLLM which
# routes to OpenRouter / OpenAI / Anthropic / Google / Mistral /
# DeepSeek / Groq / Cohere / Bedrock / etc. based on the keys you
# set on the litellm service above. The master key is internal
# sidecar auth; customers never see it.
AF_STACK_LITELLM_URL: ${AF_STACK_LITELLM_URL:-http://litellm:4000}
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY:-sk-litellm-dev}
# Runtime sees provider-key presence so AF_STACK_DEMO_MODE=auto can
# switch from deterministic demo mode to the LiteLLM-backed provider.
# LiteLLM still owns the actual upstream provider calls.
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
# First-run demo mode: "auto" uses the deterministic demo provider only
# when no real provider key is present. Adding OPENROUTER_API_KEY /
# OPENAI_API_KEY / etc. switches the same endpoint to LiteLLM.
AF_STACK_DEMO_MODE: ${AF_STACK_DEMO_MODE:-auto}
# Gateway guardrails: regex PII redaction is on by default.
# Set AF_STACK_PII_PROVIDER=presidio and Presidio URLs to use
# a Presidio analyzer/anonymizer sidecar.
AF_STACK_GUARDRAILS_ENABLED: ${AF_STACK_GUARDRAILS_ENABLED:-true}
AF_STACK_PII_REDACTION_ENABLED: ${AF_STACK_PII_REDACTION_ENABLED:-true}
AF_STACK_PII_PROVIDER: ${AF_STACK_PII_PROVIDER:-regex}
AF_STACK_PRESIDIO_ANALYZER_URL: ${AF_STACK_PRESIDIO_ANALYZER_URL:-}
AF_STACK_PRESIDIO_ANONYMIZER_URL: ${AF_STACK_PRESIDIO_ANONYMIZER_URL:-}
AF_STACK_MODERATION_ENABLED: ${AF_STACK_MODERATION_ENABLED:-true}
AF_STACK_MODERATION_BLOCK_PATTERNS: ${AF_STACK_MODERATION_BLOCK_PATTERNS:-}
# OAuth-on-behalf-of-user. These are distinct from dashboard
# sign-in OAuth vars; agents use these grants to call third-party
# APIs as the user.
AF_STACK_PUBLIC_URL: ${AF_STACK_PUBLIC_URL:-}
AF_STACK_OAUTH_ALLOWED_RETURN_ORIGINS: ${AF_STACK_OAUTH_ALLOWED_RETURN_ORIGINS:-}
OAUTH_GOOGLE_CLIENT_ID: ${OAUTH_GOOGLE_CLIENT_ID:-}
OAUTH_GOOGLE_CLIENT_SECRET: ${OAUTH_GOOGLE_CLIENT_SECRET:-}
OAUTH_GITHUB_CLIENT_ID: ${OAUTH_GITHUB_CLIENT_ID:-}
OAUTH_GITHUB_CLIENT_SECRET: ${OAUTH_GITHUB_CLIENT_SECRET:-}
# MinIO-backed object storage.
AF_STACK_S3_ADAPTER: minio
AF_STACK_S3_ENDPOINT: minio:9000
AF_STACK_S3_BUCKET: ${AF_STACK_S3_BUCKET:-af-stack}
AF_STACK_S3_ACCESS_KEY: ${AF_STACK_S3_ACCESS_KEY:-minio}
AF_STACK_S3_SECRET_KEY: ${AF_STACK_S3_SECRET_KEY:-minio-secret}
AF_STACK_S3_REGION: us-east-1
LOG_FORMAT: json
LOG_LEVEL: info
# Sandbox adapter (Phase 9.1). docker = local Docker daemon;
# requires /var/run/docker.sock mounted below. Set to "noop" or
# leave the socket unmounted to disable sandbox in CI.
AF_STACK_SANDBOX_ADAPTER: ${AF_STACK_SANDBOX_ADAPTER:-docker}
# Native browser tool. Point BROWSER_USE_URL at a sidecar speaking
# the browser-use contract (reference impl behind the opt-in
# `browser` profile below: docker compose --profile browser up).
# ALLOW_PRIVATE is needed when the sidecar lives on the compose
# network — the SSRF guard blocks private addresses by default.
AF_STACK_TOOL_BROWSER: ${AF_STACK_TOOL_BROWSER:-browser-use}
BROWSER_USE_URL: ${BROWSER_USE_URL:-}
AF_STACK_BROWSER_ALLOW_PRIVATE: ${AF_STACK_BROWSER_ALLOW_PRIVATE:-}
# Optional: enable OTel by uncommenting after OTel collector lands.
# OTEL_EXPORTER_OTLP_ENDPOINT: otel-collector:4317
# OTEL_SERVICE_NAME: af-stack
ports:
- "${AF_STACK_PORT:-8080}:8080"
- "${AF_STACK_METRICS_PORT:-9090}:9090"
# The Docker sandbox adapter needs to talk to the host daemon over the
# mounted socket. The distroless image ships with a nonroot user, but
# the macOS docker.sock is owned by the host uid (typically 501), so
# we drop to root here in DEV ONLY. Production should use the gVisor,
# Firecracker, or e2b adapter instead — none of which need docker.sock.
user: "0:0"
volumes:
# Mount the app's config.yaml (module enable flags, adapter choices)
# at the runtime's expected lookup path. Optional — when missing
# the runtime falls back to defaults + env overrides.
- ./apps/backend/config.yaml:/config.yaml:ro
# Declarative workload modules (PRD R2): the runtime discovers
# backai.module.yaml manifests here, applies their migrations and
# serves tenant-scoped CRUD under /api/v1/workload/<id>/. Enable a
# discovered module with AF_STACK_WORKLOAD_MODULES=<id>[,<id>...]
# (or enabled: true in its manifest).
- ./workload-modules:/app/workload-modules:ro
# Docker socket for the sandbox adapter. Without this mount the
# sandbox falls back to "unconfigured" and run endpoints return 503.
# In production, prefer gVisor or e2b for stronger isolation.
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
postgres:
condition: service_healthy
agentfield:
condition: service_started
litellm:
condition: service_started
restart: unless-stopped
# Note: distroless image has no shell, so we rely on Docker process
# health rather than an exec healthcheck. Compose will treat the
# container as healthy once it stays up.
# Reference browser sidecar for the native `browser` tool (headless
# Chromium via Playwright — see examples/adapters/browser-use-sidecar).
# Opt-in because the image is heavy: docker compose --profile browser up
browser-sidecar:
build: ./examples/adapters/browser-use-sidecar
profiles: ["browser"]
restart: unless-stopped
# Next.js operator console. Reaches the runtime over docker DNS;
# browser hits localhost. Auth (better-auth) shares the same Postgres.
dashboard:
build:
context: .
dockerfile: apps/dashboard/Dockerfile
environment:
DATABASE_URL: postgres://afstack:afstack@postgres:5432/afstack?sslmode=disable
AF_STACK_AUTH_SECRET: ${AF_STACK_AUTH_SECRET:-dev-secret-change-me-in-prod}
# Deployment mode. In "personal" the dashboard middleware skips the
# operator login and the billing surface is hidden. Read server-side
# only (not NEXT_PUBLIC) so it stays a true runtime toggle.
AF_STACK_MODE: ${AF_STACK_MODE:-saas}
# Default operator account, seeded on first boot so the console is
# usable immediately (no signup wizard). CHANGE THE PASSWORD after your
# first login. Seeding only runs while suite_operators is empty, so
# editing these after first boot has no effect.
AF_STACK_DEFAULT_OPERATOR_EMAIL: ${AF_STACK_DEFAULT_OPERATOR_EMAIL:-operator@af-stack.local}
AF_STACK_DEFAULT_OPERATOR_PASSWORD: ${AF_STACK_DEFAULT_OPERATOR_PASSWORD:-changeme123}
AF_STACK_DEFAULT_OPERATOR_NAME: ${AF_STACK_DEFAULT_OPERATOR_NAME:-Default Operator}
# Tell better-auth which URL the operator hits the dashboard at.
BETTER_AUTH_URL: http://localhost:${AF_STACK_DASHBOARD_PORT:-33000}
BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:${AF_STACK_CUSTOMER_APP_PORT:-34000},http://localhost:${AF_STACK_DASHBOARD_PORT:-33000}}
# Server-side fetches use Docker DNS.
RUNTIME_URL: http://runtime:8080
# Client-side fetches use the host-mapped runtime port.
NEXT_PUBLIC_RUNTIME_URL: http://localhost:${AF_STACK_PORT:-8080}
NEXT_PUBLIC_RUNTIME_UI_URL: http://localhost:${AGENTFIELD_PORT:-8081}
NEXT_PUBLIC_DASHBOARD_URL: http://localhost:${AF_STACK_DASHBOARD_PORT:-33000}
CUSTOMER_APP_URL: http://localhost:${AF_STACK_CUSTOMER_APP_PORT:-34000}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
AF_STACK_SSO_LABEL: ${AF_STACK_SSO_LABEL:-}
AF_STACK_SSO_ISSUER: ${AF_STACK_SSO_ISSUER:-}
AF_STACK_SSO_DISCOVERY_URL: ${AF_STACK_SSO_DISCOVERY_URL:-}
AF_STACK_SSO_CLIENT_ID: ${AF_STACK_SSO_CLIENT_ID:-}
AF_STACK_SSO_CLIENT_SECRET: ${AF_STACK_SSO_CLIENT_SECRET:-}
AF_STACK_SSO_SCOPES: ${AF_STACK_SSO_SCOPES:-}
ports:
- "${AF_STACK_DASHBOARD_PORT:-33000}:3000"
depends_on:
postgres:
condition: service_healthy
runtime:
condition: service_started
restart: unless-stopped
# Next.js customer product. This is the first browser target for the
# template: sign up, draft a SupportDesk reply, then deep-link to the
# matching admin cost event.
customer-app:
build:
context: .
dockerfile: apps/customer-app/Dockerfile
environment:
DATABASE_URL: postgres://afstack:afstack@postgres:5432/afstack?sslmode=disable
AF_STACK_AUTH_SECRET: ${AF_STACK_AUTH_SECRET:-dev-secret-change-me-in-prod}
# Deployment mode. In "personal" the customer app skips sign-in and
# boots straight into the product as a single implicit user. Read
# server-side only (not NEXT_PUBLIC) so it stays a true runtime toggle.
AF_STACK_MODE: ${AF_STACK_MODE:-saas}
BETTER_AUTH_URL: http://localhost:${AF_STACK_CUSTOMER_APP_PORT:-34000}
BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:${AF_STACK_CUSTOMER_APP_PORT:-34000},http://localhost:${AF_STACK_DASHBOARD_PORT:-33000}}
# Server-side proxy calls use Docker DNS.
RUNTIME_URL: http://runtime:8080
# Browser-visible URLs used in copyable snippets and admin link-outs.
NEXT_PUBLIC_RUNTIME_URL: http://localhost:${AF_STACK_PORT:-8080}
NEXT_PUBLIC_OPERATOR_URL: http://localhost:${AF_STACK_DASHBOARD_PORT:-33000}
NEXT_PUBLIC_RUNTIME_UI_URL: http://localhost:${AGENTFIELD_PORT:-8081}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
AF_STACK_SSO_LABEL: ${AF_STACK_SSO_LABEL:-}
AF_STACK_SSO_ISSUER: ${AF_STACK_SSO_ISSUER:-}
AF_STACK_SSO_DISCOVERY_URL: ${AF_STACK_SSO_DISCOVERY_URL:-}
AF_STACK_SSO_CLIENT_ID: ${AF_STACK_SSO_CLIENT_ID:-}
AF_STACK_SSO_CLIENT_SECRET: ${AF_STACK_SSO_CLIENT_SECRET:-}
AF_STACK_SSO_SCOPES: ${AF_STACK_SSO_SCOPES:-}
ports:
- "${AF_STACK_CUSTOMER_APP_PORT:-34000}:3000"
depends_on:
postgres:
condition: service_healthy
runtime:
condition: service_started
restart: unless-stopped
# Product AgentField agent for the first-run SupportDesk flow. This is the
# default proof that BackAI is not only an LLM proxy: the customer action
# goes through registered reasoners before the final LLM gateway call.
supportdesk-agent:
build:
context: apps/backend/agents/supportdesk
dockerfile: Dockerfile
environment:
AGENTFIELD_SERVER: http://agentfield:8080
NODE_ID: supportdesk
AGENT_CALLBACK_URL: http://supportdesk-agent:8090
depends_on:
agentfield:
condition: service_started
restart: unless-stopped
# Optional heavier AgentField sample with coding harness probes. It is useful
# for advanced agent/harness development, but it is intentionally outside the
# default first-run stack so the public quickstart stays light.
sample-agent:
profiles: ["advanced"]
build:
context: apps/backend/agents/sample
dockerfile: Dockerfile
environment:
AGENTFIELD_SERVER: http://agentfield:8080
NODE_ID: sample
AGENT_CALLBACK_URL: http://sample-agent:8090
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
depends_on:
agentfield:
condition: service_started
restart: unless-stopped
# OpenTelemetry collector (Phase 1.7). Bundled but commented until runtime ships.
# otel-collector:
# image: otel/opentelemetry-collector-contrib:latest
# command: ["--config=/etc/otel-collector-config.yaml"]
# volumes:
# - ./deploy/otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
# ports:
# - "4317:4317" # OTLP gRPC
# - "4318:4318" # OTLP HTTP
# Caddy reverse proxy + automatic TLS (production only).
# See docker-compose.prod.yml.
volumes:
postgres-data:
minio-data: