-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
231 lines (220 loc) · 9.29 KB
/
Copy pathdocker-compose.yml
File metadata and controls
231 lines (220 loc) · 9.29 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
# Caura Local Development Stack
# Usage: docker compose up -d
services:
# ── Infrastructure ──────────────────────────────────────────────
db:
image: pgvector/pgvector:pg16
environment:
POSTGRES_USER: caura
POSTGRES_PASSWORD: changeme
POSTGRES_DB: caura
ports:
- "${DB_PORT:-5432}:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U caura"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# ── Core Storage API (PostgreSQL CRUD) ──────────────────────────
storage-secret-init:
image: ghcr.io/caura-ai/caura-memclaw-core-storage-api:${CAURA_VERSION:-${MEMCLAW_VERSION:-latest}} # legacy-name-ok: rule 3 dual-read alias
pull_policy: missing
build:
context: .
dockerfile: core-storage-api/Dockerfile
user: "0:0"
entrypoint: ["python", "-c"]
command:
- |
from pathlib import Path
import secrets
path = Path("/run/caura-storage/shared-secret")
if not path.exists() or not path.read_text().strip():
path.write_text(secrets.token_urlsafe(48))
path.chmod(0o444)
volumes:
- storage_secret:/run/caura-storage
core-storage-api:
# Pulls the published multi-arch image from ghcr.io.
#
# ``pull_policy: missing`` is required because Compose's default
# when both ``image:`` and ``build:`` are present is to BUILD
# locally and ignore the registry — the opposite of what we want.
# ``missing`` pulls only when the image is absent from the local
# cache: first ``docker compose up`` fetches; subsequent ``up``
# commands re-use the cached image (fast, no registry round-trip,
# works offline). Crucially, this means a moving tag like
# ``:latest`` does NOT silently upgrade on every ``up`` — version
# changes are explicit. Upgrade path:
#
# docker compose pull && docker compose up -d
#
# The ``build:`` block is for ``docker compose up --build
# --pull never`` (``--pull never`` is needed when no local image is
# cached and you want to build from source without network: without
# it, ``pull_policy: missing`` triggers a pull attempt before the
# build starts and fails if the registry is unreachable. When the
# image already exists locally, ``--build`` alone is sufficient to
# force a rebuild.) Compose does NOT auto-fall-back to ``build:``
# on pull failure; an unreachable registry hard-fails ``up`` only
# when no local image is cached.
#
# Pin a specific version with ``CAURA_VERSION=v1.2.3`` in your ``.env``
# (``MEMCLAW_VERSION`` is still read); defaults to ``latest``. # legacy-name-ok: rule 3 dual-read alias
#
# The nested default is deliberate and needs Compose v2, which README.md
# already requires. Compose v2 resolves a ``${...}`` inside a default
# recursively — verified for all four combinations of the two names, and
# pinned by ``tests/test_env_dual_read.py`` so the fallback cannot be
# dropped silently. Do not flatten it to a single name: dropping the old
# one strands every install that pins with it today.
image: ghcr.io/caura-ai/caura-memclaw-core-storage-api:${CAURA_VERSION:-${MEMCLAW_VERSION:-latest}} # legacy-name-ok: rule 3 dual-read alias
pull_policy: missing
build:
context: .
dockerfile: core-storage-api/Dockerfile
environment:
DATABASE_URL: postgresql+asyncpg://caura:changeme@db:5432/caura
POSTGRES_HOST: db
POSTGRES_PORT: 5432
POSTGRES_USER: caura
POSTGRES_PASSWORD: changeme
POSTGRES_DB: caura
POSTGRES_REQUIRE_SSL: "false"
ENVIRONMENT: development
LOG_FORMAT_JSON: "false"
CORE_STORAGE_SHARED_SECRET: "${CORE_STORAGE_SHARED_SECRET:-}"
CORE_STORAGE_SHARED_SECRET_FILE: /run/caura-storage/shared-secret
volumes:
- storage_secret:/run/caura-storage:ro
depends_on:
storage-secret-init:
condition: service_completed_successfully
db:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8002/readyz')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# ── Core API (main application) ─────────────────────────────────
core-api:
# Same pull-on-first-up + ``pull_policy: missing`` pattern as
# core-storage-api above. See its block for the full rationale.
image: ghcr.io/caura-ai/caura-memclaw-core-api:${CAURA_VERSION:-${MEMCLAW_VERSION:-latest}} # legacy-name-ok: rule 3 dual-read alias
pull_policy: missing
build:
context: .
dockerfile: core-api/Dockerfile
ports:
- "${API_PORT:-8000}:8000"
env_file:
- env.dev
- path: .env
required: false
environment:
# Override DB host for Docker network
POSTGRES_HOST: db
CORE_STORAGE_API_URL: http://core-storage-api:8002
CORE_STORAGE_SHARED_SECRET: "${CORE_STORAGE_SHARED_SECRET:-}"
CORE_STORAGE_SHARED_SECRET_FILE: /run/caura-storage/shared-secret
REDIS_URL: redis://redis:6379/0
# Provider keys and embedding config (OPENAI_API_KEY, ANTHROPIC_API_KEY,
# OPENROUTER_API_KEY, OPENAI_EMBEDDING_BASE_URL / _MODEL / _SEND_DIMENSIONS /
# _TRUNCATE_TO_DIM, EMBEDDING_QUERY_INSTRUCTION) come from ``env_file``
# (.env) directly — intentionally NOT re-declared here. A "${VAR:-}"
# passthrough would (a) inject an empty string when the var is set only in
# .env, defeating the code's own defaults (the OPENAI_EMBEDDING_MODEL → 400
# break), and (b) let a stale value exported in the operator's shell SHADOW
# the value they set in .env, since Compose gives the shell precedence over
# ``.env`` inside "${...}" substitution. ``env_file`` is not shell-shadowed.
#
# The code supplies the right defaults when a var is absent — notably
# OPENAI_EMBEDDING_SEND_DIMENSIONS defaults to "true" in
# ``common/embedding/_registry.py``, so dropping its passthrough keeps the
# hosted-OpenAI default AND lets local-embedder (``--profile embed-local``)
# users set it to "false" in .env without the passthrough overriding them
# (which previously tripped the base_url + send_dimensions hard-fail).
depends_on:
core-storage-api:
condition: service_healthy
redis:
condition: service_healthy
tei:
condition: service_healthy
required: false # only enforced when --profile embed-local is active
volumes:
- storage_secret:/run/caura-storage:ro
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/health')"]
interval: 15s
# Must stay above core-api's PROBE_TIMEOUT_SECONDS (12s): the probe is
# allowed to spend that long bounding a stalled dependency, and a shorter
# timeout here severs the check and scores a failure before the probe can
# answer — the same inverted-budget defect, one layer out. At 5s a slow
# storage connect read as an unhealthy container.
timeout: 15s
retries: 3
start_period: 15s
# ── Local Embedder (opt-in via ``--profile embed-local``) ───────
#
# HuggingFace Text Embeddings Inference server. Drop-in replacement
# for the OpenAI embeddings API: ``OPENAI_EMBEDDING_BASE_URL=
# http://tei:80/v1`` on ``core-api`` routes embeddings here instead
# of api.openai.com. Default model is ``BAAI/bge-m3`` (1024-dim,
# MIT, multilingual, no instruction prefix needed) — matches the
# schema set in alembic migration 012.
#
# Default image is the CPU build (``cpu-1.7``); for an L4-class
# GPU box, set ``TEI_IMAGE_TAG=89-1.7`` and add the deploy/gpu
# block (commented at the bottom of this service). See
# ``docs/local-embedder.md`` for hardware sizing and model swaps.
tei:
image: ghcr.io/huggingface/text-embeddings-inference:${TEI_IMAGE_TAG:-cpu-1.7}
profiles: ["embed-local"]
command:
- --model-id
- "${TEI_MODEL_ID:-BAAI/bge-m3}"
- --auto-truncate
environment:
HUGGINGFACE_HUB_CACHE: /data
volumes:
- tei_cache:/data
healthcheck:
# Generous retries: first-time model download (~2 GB for
# bge-m3) can take several minutes on a cold cache.
test: ["CMD", "curl", "-fs", "http://localhost:80/health"]
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
# Uncomment to attach a GPU (L4, A10, etc) — requires
# nvidia-container-toolkit on the host. Verified on L4
# (``g2-standard-8``) in the local-embedder bench.
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu]
volumes:
pgdata:
redis_data:
storage_secret:
tei_cache: