-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
182 lines (168 loc) · 7.79 KB
/
Copy pathdocker-compose.yml
File metadata and controls
182 lines (168 loc) · 7.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
# ============================================
# DeepTutor Docker Compose Configuration
# ============================================
# This file provides orchestration for DeepTutor services
#
# Usage:
# Production: python scripts/docker_compose.py up -d
# Development: python scripts/docker_compose.py -f docker-compose.yml -f docker-compose.dev.yml up
# Build only: python scripts/docker_compose.py build
#
# Prerequisites:
# 1. Runtime settings are stored in ./data/user/settings (created automatically)
# 2. Configure model providers from the web Settings page or model_catalog.json
# 3. Use scripts/docker_compose.py so port mappings are rendered from system.json
#
# Local LLM (LM Studio / Ollama / vLLM):
# Use "host.docker.internal" instead of "localhost" in provider base_url fields.
# Configure provider endpoints in data/user/settings/model_catalog.json or the UI.
# ============================================
services:
# ============================================
# PocketBase — optional auth + storage sidecar
# Set integrations.pocketbase_url=http://pocketbase:8090 to activate.
# Leave integrations.pocketbase_url blank to run without PocketBase (SQLite fallback).
# ============================================
pocketbase:
image: ghcr.io/muchobien/pocketbase:latest
container_name: pocketbase
restart: unless-stopped
ports:
- "${DEEPTUTOR_DOCKER_POCKETBASE_PORT:-8090}:8090"
volumes:
# The upstream pocketbase image's entrypoint uses absolute paths
# (no /pb/ prefix): --dir=/pb_data --publicDir=/pb_public
# --hooksDir=/pb_hooks. The previous example mounted at /pb/pb_data
# and crashed on first start with "mkdir /pb_data: read-only file
# system".
- ./data/pocketbase:/pb_data
- ./data/pocketbase/public:/pb_public
- ./data/pocketbase/hooks:/pb_hooks
networks:
- deeptutor-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8090/api/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
# ============================================
# All-in-One DeepTutor Service
# ============================================
deeptutor:
build:
context: .
dockerfile: Dockerfile
target: production
container_name: deeptutor
restart: unless-stopped
ports:
- "${DEEPTUTOR_DOCKER_BACKEND_PORT:-8001}:${DEEPTUTOR_DOCKER_BACKEND_PORT:-8001}"
- "${DEEPTUTOR_DOCKER_FRONTEND_PORT:-3782}:${DEEPTUTOR_DOCKER_FRONTEND_PORT:-3782}"
volumes:
# Everything DeepTutor persists lives under ./data — one tree to mount
# and back up: admin workspace + runtime settings (data/user), per-user
# workspaces (data/users), partners (data/partners), accounts/grants/
# audit (data/system), knowledge bases, memory.
# The sandbox-runner gets a narrower slice of this volume: never
# data/system, which holds auth state, provider API keys, and per-owner
# secrets. What it *does* carry for non-admin accounts is wider than
# workspace subtrees — see the scope note on that service's own volumes.
- ./data:/app/data
environment:
# Route untrusted shell exec to the runner sidecar (SYSTEM isolation).
# When this is set, the main app NEVER runs untrusted code in its own
# process — see deeptutor/services/sandbox/config.py:build_backend().
# On bare-metal / macOS deployments where the runner is not started, leave
# this unset: the app degrades to bwrap (Linux, if installed) or, only when
# DEEPTUTOR_SANDBOX_ALLOW_SUBPROCESS=1, a restricted subprocess.
- DEEPTUTOR_SANDBOX_RUNNER_URL=http://sandbox-runner:8900
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${DEEPTUTOR_DOCKER_BACKEND_PORT:-8001}/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
pocketbase:
condition: service_healthy
sandbox-runner:
condition: service_healthy
networks:
- deeptutor-network
# ============================================
# Sandbox Runner Sidecar
# ============================================
# Executes untrusted shell commands in an isolated, least-privileged
# container so the main app never has to. Rationale for the sidecar shape:
# * the main `deeptutor` container keeps minimal privileges and does NOT
# mount the docker socket or run untrusted code in its own process;
# * a sandbox escape here lands in a stripped, unprivileged container with
# no app secrets, not in the main app.
# Not exposed to the host: it is reachable only on the internal
# deeptutor-network at http://sandbox-runner:8900.
sandbox-runner:
build:
context: .
dockerfile: Dockerfile.runner
container_name: deeptutor-sandbox-runner
restart: unless-stopped
# Share the task workspaces at the SAME paths as the main app so the mount
# contract holds: a request with host_path == sandbox_path under
# /app/data/user/workspace (admin) or /app/data/users/<uid> (per-user)
# is already visible here, no per-command mounting needed.
# Scope note: every command in this container shares one filesystem view
# (per-command isolation is a roadmap item). The admin mount is narrowed to
# its workspace subtree, but the per-user mount is a whole user root, so for
# non-admin accounts it also carries data/users/<uid>/user/settings,
# user/chat_history.db, and knowledge_bases — every account's exec can read
# every other account's copy of those. That cross-user visibility is
# accepted for the invite-only trust posture and enforced no further.
# data/system is never mounted here, which is why credentials that authorize
# a person (Codex OAuth tokens) live under data/system/user-secrets/<owner>
# instead of inside a user root — and why installed CLI apps live in a third
# place, data/cli-apps, rather than in either: the runner has to *execute*
# them, so data/system is out; it must not be able to *modify* them, so a
# workspace subtree is out too.
volumes:
- ./data/user/workspace:/app/data/user/workspace
- ./data/users:/app/data/users
# Installed CLI apps, READ-ONLY. This is the one directory the runner
# executes code from that it did not receive in a request, so it must not
# be writable here: an escaped command that could overwrite an app's entry
# point would run as that app in every later turn, for every account.
# Installing writes it from the main container instead (services/cli_apps).
- ./data/cli-apps:/app/data/cli-apps:ro
# No `ports:` — intentionally not published to the host.
# ----- Security hardening -----
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Read-only root filesystem: the runner only needs to read its own code and
# write under the shared volume (and scratch space). Give it tmpfs for the
# paths shell tooling expects to be writable (/tmp, $HOME). If a workload
# needs more writable scratch, relax read_only rather than widening tmpfs.
read_only: true
tmpfs:
- /tmp
- /home/runner
pids_limit: 256
mem_limit: 1g
# cgroup-enforced RSS cap is the authoritative memory backstop; the
# per-command RLIMIT_AS in server.py is a cheaper secondary guard.
healthcheck:
test: ["CMD", "python", "-c",
"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8900/health',timeout=3).status==200 else 1)"]
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
networks:
- deeptutor-network
# ============================================
# Networks
# ============================================
networks:
deeptutor-network:
driver: bridge