-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
213 lines (204 loc) · 9.79 KB
/
Copy pathdocker-compose.yml
File metadata and controls
213 lines (204 loc) · 9.79 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
# Odek in Docker Compose — Restricted (default) and Godmode (all permissions).
#
# Run from this directory (`cd docker`) so the relative paths and `.env`
# resolve correctly. Build context is the repo root (one level up).
#
# Profiles (opt into exactly one at a time):
# restricted Web UI + approval prompts (recommended)
# godmode No prompts; disposable container
# telegram-restricted Telegram bot; approvals via inline keyboards
# telegram-godmode Telegram bot; no prompts
#
# Examples:
# docker compose --profile restricted up --build
# docker compose --profile godmode run --rm odek-godmode run "create build.sh and run it"
# docker compose --profile telegram-restricted up --build -d
services:
# ── Restricted (default) — interactive Web UI with approval prompts ──
odek-restricted:
profiles: ["restricted"]
build:
context: ..
dockerfile: docker/Dockerfile
image: odek:local
env_file: .env
command: ["serve", "--addr", "0.0.0.0:8080", "--no-sandbox"]
ports:
- "127.0.0.1:8080:8080" # Web UI, bound to localhost only
volumes:
- ./workspace:/workspace
- ./config.restricted.json:/home/odek/.odek/config.json:ro
restart: "no"
depends_on: [searxng, llama-embeddings, piguard-gateway]
# ── Godmode (all permissions) — non-interactive, disposable container ──
odek-godmode:
profiles: ["godmode"]
build:
context: ..
dockerfile: docker/Dockerfile
image: odek:local
env_file: .env
# No published ports (no inbound needed). The container keeps default
# outbound networking — Odek MUST reach the LLM provider API to run.
# Isolation here is the container boundary + non-root + only ./workspace
# mounted, NOT network restriction. To also fence the agent's own egress,
# put it on a network behind an allowlisting proxy (advanced; see README).
command: ["serve", "--addr", "0.0.0.0:8080", "--no-sandbox"]
volumes:
- ./workspace:/workspace
- ./config.godmode.json:/home/odek/.odek/config.json:ro
restart: "no"
depends_on: [searxng, llama-embeddings, piguard-gateway]
# ── Telegram bot — Restricted (approvals via inline keyboards) ──
# State (sessions, skills, telegram.pid) lives in the local ./.odek folder —
# an external folder on the host, just like ./workspace. config.restricted.json
# is layered on top of ./.odek/config.json to set the approval policy.
odek-telegram-restricted:
profiles: ["telegram-restricted"]
build:
context: ..
dockerfile: docker/Dockerfile
image: odek:local
env_file: .env
command: ["telegram"]
# init: true runs Docker's built-in init (tini) as PID 1, which reaps any
# child processes the agent spawns (shell tool, sub-agents) and forwards
# SIGTERM from `docker stop` for a clean shutdown. The bot also hosts the
# native scheduler (see docs/SCHEDULES.md) — jobs live in ./.odek/schedules.json.
init: true
volumes:
- ./workspace:/workspace
- ./.odek:/home/odek/.odek
- ./config.restricted.json:/home/odek/.odek/config.json:ro
restart: unless-stopped
depends_on: [searxng, llama-embeddings, piguard-gateway]
# ── Telegram bot — Godmode (no prompts; unrestricted) ──
# Same ./.odek state folder; config.godmode.json is layered on top of
# ./.odek/config.json to force the unrestricted (action: allow) policy.
odek-telegram-godmode:
profiles: ["telegram-godmode"]
build:
context: ..
dockerfile: docker/Dockerfile
image: odek:local
env_file: .env
command: ["telegram"]
init: true # reap agent child processes + forward SIGTERM (see telegram-restricted)
volumes:
- ./workspace:/workspace
- ./.odek:/home/odek/.odek
- ./config.godmode.json:/home/odek/.odek/config.json:ro
restart: unless-stopped
depends_on: [searxng, llama-embeddings, piguard-gateway]
# ── llama.cpp embeddings — local backend for the shared "embedding" block ──
# Co-starts with every odek profile so semantic embeddings work out of the box
# — no cloud embeddings API, no keys. Both bundled configs set the top-level
# "embedding" block to this server, so a single endpoint powers ALL three
# consumers: memory (recall, dedup, ranking, fact merge), session_search, and
# lazy skill matching (skills inherit the shared default with a bounded 2s
# per-turn timeout). Reachable only by the odek containers over the compose
# network at http://llama-embeddings:8080 — NO host port is published. The
# GGUF is baked into the image (see docker/Dockerfile.embeddings), so there is
# no first-run download and no volume. To run without local embeddings,
# comment this service and the matching depends_on entries, and drop the
# top-level "embedding" block from the configs (every subsystem then falls
# back to local RandomProjections — lexical, zero-cost).
llama-embeddings:
profiles: ["restricted", "godmode", "telegram-restricted", "telegram-godmode"]
build:
context: ..
dockerfile: docker/Dockerfile.embeddings
image: odek-embeddings:local
# Health is observability only — odek tolerates the server being briefly
# unavailable (recall degrades to "no context" and rebuilds back off 30s),
# so startup is not gated on it. The model is bundled, so load is quick.
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/health"]
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
restart: unless-stopped
# ── SearXNG — self-hosted metasearch backing the `web_search` tool ──
# Co-starts with every odek profile so web search works out of the box.
# Reachable only by the odek containers over the compose network at
# http://searxng:8080 — NO host port is published (the agent is the only
# consumer). The bundled config enables the JSON API and disables the
# anti-bot limiter, so no Redis/Valkey is needed (single trusted consumer).
# Set SEARXNG_SECRET in .env. To run without web search, comment this service
# and the depends_on lines above, and drop "web_search" from the configs.
searxng:
profiles: ["restricted", "godmode", "telegram-restricted", "telegram-godmode"]
image: searxng/searxng:2026.6.8-f3fab143b # pinned; bump deliberately
environment:
- SEARXNG_BASE_URL=http://searxng:8080/
# SearXNG reads server.secret_key from this env var at app load, overriding
# settings.yml. Set SEARXNG_SECRET in .env. The fallback must NOT be the
# "ultrasecretkey" sentinel: in production (non-debug) mode SearXNG treats
# that literal as FATAL — it logs "secret_key is not changed" and calls
# sys.exit(1), so the worker dies and the container never serves. This
# internal-only instance (no host port, single trusted consumer) is fine
# with a static default; override it in .env for any exposed deployment.
- SEARXNG_SECRET=${SEARXNG_SECRET:-odek-searxng-internal-default-override-in-env}
volumes:
- ./searxng/settings.yml:/etc/searxng/settings.yml:ro
# SearXNG needs outbound internet to query upstream engines (Google, Bing,
# DuckDuckGo, …). Behind the advanced allowlisting egress proxy, permit those.
restart: unless-stopped
# ── PIGuard prompt-injection sidecar ─────────────────────────────────────
# Optional semantic second-opinion guard for memory, system prompt, MCP
# descriptions, skills, Telegram, and tool outputs. The daemon loads a
# one-time-exported ONNX model (~735 MB); run ./piguard/download-model.sh
# before starting an odek profile to populate ./piguard/models.
#
# The daemon speaks newline-delimited JSON over a private Unix socket; the
# gateway bridges that to plain HTTP on the compose network so odek can use
# its existing HTTP-based piguard client without sharing a socket volume.
#
# Apple Silicon note: the upstream daemon Dockerfile pins its base images by
# amd64 digest, which yields x86-64 binaries that crash under Rosetta on
# macOS. If `docker compose up` fails with "rosetta error", build the daemon
# locally from the guard repo with the digest pins removed and tag it
# piguard:local (compose reuses the existing image).
piguard:
profiles: ["restricted", "godmode", "telegram-restricted", "telegram-godmode"]
# GHCR packages for this repo are private, so we build the daemon image locally
# from the public source. The first build clones the repo and compiles the
# binary; subsequent starts reuse the cached image.
build:
context: https://github.com/BackendStack21/go-prompt-injection-guard.git#v1.0.0
dockerfile: Dockerfile
image: piguard:local
command:
- "--socket=/run/piguard/piguard.sock"
- "--model-dir=/models"
- "--max-batch=32"
- "--batch-wait=5ms"
volumes:
- ./piguard/models:/models:ro
- piguard-sock:/run/piguard
healthcheck:
test: ["CMD-SHELL", "test -S /run/piguard/piguard.sock"]
interval: 10s
timeout: 3s
retries: 5
start_period: 60s
restart: unless-stopped
piguard-gateway:
profiles: ["restricted", "godmode", "telegram-restricted", "telegram-godmode"]
# Vendored gateway (./piguard-gateway): forwards odek's JSON protocol
# ({"text"|"long"|"texts"}) verbatim to the daemon socket. The upstream
# examples/http-gateway is a raw-text demo that double-encodes odek's JSON
# bodies, making the model score the wrapper instead of the content.
build:
context: ./piguard-gateway
image: piguard-gateway:local
command: ["--addr=:8080", "--socket=/run/piguard/piguard.sock"]
volumes:
- piguard-sock:/run/piguard
depends_on:
piguard:
condition: service_healthy
restart: unless-stopped
volumes:
piguard-sock: