-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompose.yaml
More file actions
206 lines (201 loc) · 9.2 KB
/
Copy pathcompose.yaml
File metadata and controls
206 lines (201 loc) · 9.2 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
# ============================================
# DeepTutor — Podman Compose (rootless, read-only rootfs)
# ============================================
# Requires:
# * podman 4.1+ (rootless)
# * a compose provider. On podman 5.x without the native Go-based
# compose plugin, `podman compose` is a thin wrapper around
# `podman-compose` (Python). Both work; the `docker-compose` CLI
# also works if it's installed and pointed at the podman socket.
# This file is `podman-compose` 1.5+ compatible.
# * Verify with: `podman compose version` and `podman info | grep -i rootless`
#
# Bring up (this file lives next to docker-compose.yml, so always pass -f):
# cp .env.example .env # then edit
# podman compose -f compose.yaml up -d
# podman compose -f compose.yaml ps
# podman compose -f compose.yaml logs -f deeptutor
#
# Tip: alias it for the session — alias dc='podman compose -f compose.yaml'
# or export COMPOSE_FILE=compose.yaml so the default `podman compose`
# picks this file up without -f.
#
# Tear down (keeps volumes):
# podman compose -f compose.yaml down
# Wipe data (DESTRUCTIVE):
# podman compose -f compose.yaml down -v
#
# Top-level choices (see git log / PR for full rationale):
# * All services run with `read_only: true`. The `tmpfs:` mounts below are
# the only writable surface inside each container's rootfs.
# * `userns_mode: keep-id` plus the `:U` suffix on every volume mount
# implements rootless-keep-uid: host UID $UID maps to container UID $UID.
# Files you create on the host are visible as your own user inside.
# * Host-side port bindings are LOOPBACK ONLY (127.0.0.1:). Drop the
# `127.0.0.1:` prefix to expose on all interfaces.
# * Two services: `deeptutor` (backend+frontend in one GHCR image, run
# under supervisord) and `pocketbase` (optional auth/storage sidecar).
# The sandbox-runner sidecar from docker-compose.yml is intentionally
# NOT included: the main app falls back to bwrap (Linux, if installed
# in the image) or the restricted subprocess backend controlled by
# the `sandbox_allow_subprocess` setting in system.json. See
# deeptutor/services/sandbox/config.py:build_backend().
# * The backend and frontend run as a non-root `deeptutor` user (UID
# 1000). supervisord runs as root (PID 1), the entrypoint chowns
# `/app/data`, and each program is dropped to `deeptutor` via its
# per-program `user=` directive, so the app processes are UID 1000
# inside the container. Under `userns_mode: keep-id` that maps to the
# host user.
# * URL knowledge lives in `data/user/settings/system.json` (in-network
# `next_public_api_base` for the typical case, or
# `next_public_api_base_external` for cloud/external). The entrypoint
# reads the JSON on every start and exports `DEEPTUTOR_API_BASE_URL`,
# which `web/proxy.ts` reads to rewrite `/api/*` and `/ws/*` to the
# configured backend at request time. There is no build-time
# placeholder, no runtime `sed -i` on the bundle, and no compose env
# var for the API base.
# * Runtime settings (ports, auth, model catalog, integrations) live in
# data/user/settings/*.json INSIDE the deeptutor-data volume. The
# entrypoint unsets BACKEND_PORT, FRONTEND_PORT, NEXT_PUBLIC_API_BASE,
# NEXT_PUBLIC_API_BASE_EXTERNAL, AUTH_ENABLED, POCKETBASE_URL, etc.
# and re-exports them from the JSONs on every start. So: edit JSONs
# + `podman compose restart deeptutor`, NOT compose env vars.
# ============================================
name: deeptutor
services:
# ----------------------------------------------------------
# PocketBase — optional auth + storage sidecar
# ----------------------------------------------------------
# Activated by setting `integrations.pocketbase_url` to
# `http://pocketbase:8090` in data/user/settings/integrations.json.
# Leave blank to run with the SQLite fallback (single-user / invite-only).
pocketbase:
image: ghcr.io/muchobien/pocketbase:latest
container_name: deeptutor-pocketbase
pull_policy: always
restart: unless-stopped
userns_mode: keep-id
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
- /run:size=16m,mode=0755
- /var/run:size=8m,mode=0755
ports:
- "127.0.0.1:${HOST_PORT_POCKETBASE:-8090}:8090"
volumes:
# Bind mount a host directory so the host user (UID $UID) owns
# the SQLite DB and pocketbase's public/hooks dirs. With
# userns_mode: keep-id, the process inside is also UID $UID, so
# reads and writes line up. (The pocketbase image's entrypoint
# uses --dir=/pb_data --publicDir=/pb_public --hooksDir=/pb_hooks
# — absolute paths, no /pb/ prefix.)
- ./data/pocketbase:/pb_data:U
- ./data/pocketbase/public:/pb_public:U
- ./data/pocketbase/hooks:/pb_hooks:U
networks:
- deeptutor
healthcheck:
test:
- CMD
- wget
- --quiet
- --tries=1
- --spider
- http://localhost:8090/api/health
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
# ----------------------------------------------------------
# DeepTutor — backend (FastAPI :8001) + frontend (Next.js :3782)
# ----------------------------------------------------------
# Both run inside the same image, supervised by supervisord under the
# unprivileged `deeptutor` user (UID 1000). The image's entrypoint loads
# runtime settings from data/user/settings/*.json and exports them into
# the supervisord children's environment, so changing ports/auth/providers
# means editing JSONs + `podman compose restart`.
deeptutor:
image: ghcr.io/hkuds/deeptutor:latest
container_name: deeptutor
pull_policy: always
restart: unless-stopped
userns_mode: keep-id
read_only: true
# Writable surfaces inside the RO rootfs. Each tmpfs is scoped to a
# specific need:
# /tmp — scratch for Python, uvicorn, Node, Next.js +
# supervisord's pidfile (/tmp/supervisord.pid)
# /run — standard Linux runtime dir
# /var/run — standard runtime dir (kept writable for any tooling)
# /var/log — covers any supervisord child that defaults there
# /root — catches stray $HOME-style writes (image sets
# PYTHONDONTWRITEBYTECODE=1, but be safe)
# /home — same, for any non-root code path
tmpfs:
- /tmp:size=512m,mode=1777
- /run:size=32m,mode=0755
- /var/run:size=8m,mode=0755
- /var/log:size=64m,mode=0755
- /root:size=16m,mode=0700
- /home:size=16m,mode=0755
ports:
- "127.0.0.1:${HOST_PORT_BACKEND:-8001}:8001"
- "127.0.0.1:${HOST_PORT_FRONTEND:-3782}:3782"
volumes:
# Bind mount a host directory so the host user (UID $UID) owns
# the entire data tree. With userns_mode: keep-id the process
# inside is also UID $UID, so writes from the FastAPI backend,
# Next.js, and supervisord all line up. The same path was used
# by the original docker-compose.yml; we keep it for consistency.
# The data tree holds: admin workspace + runtime settings
# (data/user), per-user workspaces (data/users), partners
# (data/partners), accounts/grants/audit (data/system),
# knowledge bases, memory, logs. One tree to back up.
- ./data:/app/data:U
environment:
# Time zone — picked up by Python (time.tzset) and Next.js at boot.
- TZ=${TZ:-UTC}
# Local LLM (LM Studio / Ollama / vLLM) base URLs in
# data/user/settings/model_catalog.json should use
# `http://host.containers.internal:PORT` (podman) — NOT localhost,
# which inside the container is the container's own loopback.
# Uncomment to enable PocketBase auth (also set integrations.json):
# - POCKETBASE_URL=http://pocketbase:8090
# - POCKETBASE_EXTERNAL_URL=http://localhost:8090
healthcheck:
test:
- CMD
- curl
- -fsS
- http://localhost:8001/
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
pocketbase:
condition: service_healthy
networks:
- deeptutor
# Optional hardening — uncomment to taste. supervisord runs as root
# (PID 1) and setuids each program down to UID 1000, which needs
# CAP_SETUID/CAP_SETGID — so do NOT `cap_drop: ALL` here, it would stop
# the backend/frontend from spawning. `no-new-privileges` is safe (a
# root process dropping its own privileges is not "gaining" any).
# security_opt:
# - no-new-privileges:true
# # cap_drop: ALL is unsafe — supervisord needs CAP_SETUID/CAP_SETGID to
# # drop its children to the deeptutor user.
# Resource limits. Honored on cgroup v2 hosts; podman warns and skips
# on cgroup v1 or no-cgroup environments.
# pids_limit: 1024
# mem_limit: 4g
# cpus: 4.0
networks:
deeptutor:
driver: bridge
# Named volumes intentionally not used: with userns_mode: keep-id the
# container process runs as the host user (UID $UID), but podman
# auto-creates named volumes with UID 100000 (userns-mapped root),
# so 755 perms + wrong owner = PermissionError on the first JSON
# write. Bind mounts on a host directory you own work cleanly.