Repository navigation
Expand file tree
/
Copy pathdocker-compose.yaml
More file actions
223 lines (218 loc) · 11.1 KB
/
Copy pathdocker-compose.yaml
File metadata and controls
223 lines (218 loc) · 11.1 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
# Omadia local development + self-hosting stack — MINIMAL CORE.
#
# `docker compose up -d` brings up only the three services every install
# needs, pulling prebuilt images from GHCR (no source build):
# - middleware (Omadia kernel API on http://localhost:8080)
# - web-ui (Admin UI on http://localhost:3333)
# - postgres (Postgres + pgvector — KG / routines / verifier store)
#
# Optional features are opt-in overlay files, composed with `-f` (each adds
# its sidecar service AND the env that activates the matching plugin):
# -f docker-compose.storage.yaml object storage (SeaweedFS) — chat attachments
# -f docker-compose.diagrams.yaml Kroki diagram rendering (needs storage too)
# -f docker-compose.embeddings.yaml in-tenant embeddings (Ollama)
# -f docker-compose.skillspector.yaml advisory plugin code scanning (SkillSpector)
# -f docker-compose.update.yaml operator-triggered self-update (#432)
# -f docker-compose.build.yaml build middleware + web-ui from source
#
# Example — full stack:
# docker compose -f docker-compose.yaml \
# -f docker-compose.storage.yaml -f docker-compose.diagrams.yaml \
# -f docker-compose.embeddings.yaml up -d
#
# Image tag: defaults to `latest` (newest release). Pin a specific version
# with `OMADIA_VERSION=vX.Y.Z` as a shell env var, or in a project-root `.env`
# file next to this compose file. Compose reads image-tag variables only from
# the shell or the root `.env`, never from middleware/.env.
#
# NOTE (#432): `OMADIA_VERSION` is deliberately NOT listed under the services'
# `environment:` below. It is baked into each image at build time and reported
# by `/health` and Admin → Update; passing it through compose would override
# that baked stamp with an empty string whenever the variable is unset.
#
# Usage:
# cp middleware/.env.example middleware/.env # set ANTHROPIC_API_KEY
# docker compose up -d # start the minimal core (pulls)
# docker compose logs -f middleware # tail middleware boot; on a fresh
# # install it prints the one-time
# # "setup token" the first-admin
# # wizard asks for
# docker compose down # stop (keeps volumes)
# docker compose down -v # stop + wipe all data
#
# All host ports bind to 127.0.0.1 only — never expose this stack on a
# public interface without an external reverse-proxy + auth layer.
services:
# --- Omadia middleware (TypeScript kernel + plugin runtime) ---------------
middleware:
image: ghcr.io/byte5ai/omadia-middleware:${OMADIA_VERSION:-latest}
restart: unless-stopped
env_file:
# Stack defaults (#1317): PUBLIC_BASE_URL and the redirect URIs, set to
# the origins this stack actually serves (UI :3333, middleware :8080) —
# the middleware's own default, :3979, has nothing listening here. Listed
# FIRST so middleware/.env below overrides any of them. They are not under
# `environment:` on purpose: that key beats every env_file.
- path: middleware/compose.defaults.env
required: true
# Optional: operator copies .env.example → .env and fills in
# ANTHROPIC_API_KEY (+ any plugin-specific creds). Compose tolerates a
# missing file when `required: false`, so a fresh clone still validates.
# The kernel boots without ANTHROPIC_API_KEY — the admin UI + setup
# wizard come up and the chat route returns 503 until a key is saved
# (via the wizard or this file).
- path: middleware/.env
required: false
environment:
# Postgres + pgvector for the knowledge graph, routines store, and
# verifier store. Without DATABASE_URL the middleware falls back to
# the in-memory KG (loses state on restart, no routines, no verifier
# persistence) — fine for tests but not what an operator who just ran
# `docker compose up` wants.
DATABASE_URL: postgresql://omadia:omadia@postgres:5432/omadia
# Request trust boundary (#1310, docs/security-architecture.md §10o).
# Both services are published on 127.0.0.1 over plain HTTP, so the
# defaults are right as they stand: TRUSTED_PROXY_ADDRESSES is empty (no
# hop may set X-Forwarded-Proto) and PUBLIC_SCHEME is `auto`, which leaves
# the auth cookies without Secure — correct, because a browser would
# discard a Secure cookie served over http://127.0.0.1. The web-ui proxy
# in front is NOT a trusted hop here, deliberately: it declares
# WEB_UI_PUBLIC_SCHEME (default http) rather than relaying the browser's
# header, so nothing it forwards needs trusting. Put a TLS-terminating
# proxy in front of the stack and you set its address here (or `loopback`
# when it runs on this host), plus PUBLIC_SCHEME=https if it sets no
# X-Forwarded-Proto. See middleware/.env.example.
# Vault master key. The shipped image bakes `NODE_ENV=production`
# (see Dockerfile), which would normally make the middleware refuse
# to boot without VAULT_KEY set — by design, because the dev
# fallback writes the master key into the data volume (encrypted
# vault → plaintext-at-rest equivalence). For the local compose
# stack we override to `development` so the dev fallback stays
# available: on first boot the middleware writes a random key into
# /data/.dev-vault.key on the persistent volume, and it's re-read
# across `docker compose restart` and rebuilds.
#
# For any deployment beyond local dev: drop the NODE_ENV override
# below AND supply your own keys via `.env` — BOTH `VAULT_KEY` and
# `CREDENTIAL_KEYCHAIN_KEY` (each `openssl rand -base64 32`, two
# different values). Keep them stable — losing them makes vault /
# keychain entries unrecoverable.
VAULT_KEY: ${VAULT_KEY:-}
# Credential-keychain master key (#578/#778). Same rules as VAULT_KEY,
# same dev fallback (/data/.dev-credential-keychain.key), but a
# separate trust domain — never reuse the VAULT_KEY value.
CREDENTIAL_KEYCHAIN_KEY: ${CREDENTIAL_KEYCHAIN_KEY:-}
NODE_ENV: ${NODE_ENV:-development}
# NB: `DEV_ENDPOINTS_ENABLED` (the session-gated /api/dev/graph +
# /api/dev/memory dev mounts) is intentionally NOT hard-coded here. The
# admin UI's Graph viewer depends on it. The routes need an operator
# login, but they hand raw KG + memory state (every user's chat
# sessions included) to any logged-in session — anyone who reuses this
# compose file as a template for a non-local deploy would otherwise
# publish that. To enable for local inspection, uncomment
# `DEV_ENDPOINTS_ENABLED=true` in `middleware/.env` (already picked up
# via `env_file` above). `DEV_ENDPOINTS_LOOPBACK_ONLY=true` additionally
# refuses every /api/dev request that is not from loopback — note that
# requests from the host and from web-ui reach this container from a
# Docker bridge address, so that flag closes the routes here entirely.
# **NEVER set DEV_ENDPOINTS_ENABLED on a public deployment.**
# Redirect kernel persistent state (vault, installed.json, builder
# drafts.db, uploaded plugin packages) to the volume mount below.
# Without this, the kernel writes under /app/data which lives in the
# image layer and gets wiped on every image pull/rebuild — all builder
# drafts disappear, uploaded plugins vanish, etc. Path must match the
# volume target. (Memory itself is Postgres- or RAM-backed.)
PLATFORM_DATA_DIR: /data
depends_on:
postgres:
condition: service_healthy
ports:
- '127.0.0.1:8080:8080'
volumes:
# Vault (encrypted secrets), memory store, builder drafts.db, uploaded
# plugin packages — everything the kernel needs to keep across restarts.
- middleware-data:/data
networks: [omadia]
healthcheck:
# node-based probe — debian-slim has neither wget nor curl pre-installed,
# and node is the one binary we're guaranteed to have.
test:
- 'CMD'
- 'node'
- '-e'
- "fetch('http://localhost:8080/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
interval: 10s
timeout: 5s
retries: 6
# First-boot includes plugin-catalog load + KG migrations + bootstrap
# auto-installs + workspace-package activations — easily 30-60s on a
# cold machine. Don't fail the healthcheck during that window.
start_period: 90s
# --- Admin UI (Next.js, talks to middleware over /bot-api) ----------------
web-ui:
image: ghcr.io/byte5ai/omadia-web-ui:${OMADIA_VERSION:-latest}
restart: unless-stopped
environment:
MIDDLEWARE_URL: http://middleware:8080
depends_on:
middleware:
condition: service_healthy
ports:
- '127.0.0.1:3333:3000'
networks: [omadia]
healthcheck:
# Same node-based probe as the middleware (#1382): the runtime image has
# neither curl nor wget. /health is static liveness, so this goes red
# only when Next itself stops serving, not when the middleware is down.
test:
- 'CMD'
- 'node'
- '-e'
- "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
# --- Postgres + pgvector (knowledge graph, routines, verifier store) -----
# Same image as Neon-style production deployments — pgvector pre-installed.
# Migrations run automatically on the middleware's first boot.
postgres:
image: pgvector/pgvector:pg17
restart: unless-stopped
# Network alias + hostname so the middleware always resolves the
# `postgres` DNS name regardless of the compose project prefix. No explicit
# container_name: it's redundant for in-network DNS (the service name +
# `networks.omadia.aliases` below already resolve `postgres`) and a fixed
# name collides when a second compose project runs side by side.
hostname: postgres
environment:
POSTGRES_USER: omadia
POSTGRES_PASSWORD: omadia
POSTGRES_DB: omadia
ports:
# Loopback-only. Operator can connect via psql from the host for
# inspection; never expose Postgres publicly.
- '127.0.0.1:5432:5432'
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
omadia:
aliases: [postgres]
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U omadia -d omadia']
interval: 5s
timeout: 3s
retries: 10
# First-boot initdb runs while pg_isready already says "ready" — give
# it a head start so the middleware doesn't race against role + DB
# creation finishing.
start_period: 15s
volumes:
middleware-data:
postgres-data:
networks:
# `omadia` is the application network: every service here and in the feature
# overlays. The update overlay adds a second, `internal` network,
# `omadia-control`, that only `updater` and `docker-socket-proxy` may join.
omadia:
driver: bridge