Files
wehub-resource-sync e4dcfc49aa
Tests / Lint and Format (push) Waiting to run
Tests / Web Node Tests (push) Waiting to run
Tests / Import Check (Python 3.11) (push) Waiting to run
Tests / Import Check (Python 3.12) (push) Waiting to run
Tests / Import Check (Python 3.13) (push) Waiting to run
Tests / Import Check (Python 3.14) (push) Waiting to run
Tests / Python Tests (Python 3.11) (push) Blocked by required conditions
Tests / Python Tests (Python 3.12) (push) Blocked by required conditions
Tests / Python Tests (Python 3.13) (push) Blocked by required conditions
Tests / Python Tests (Python 3.14) (push) Blocked by required conditions
Tests / Test Summary (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

169 lines
6.8 KiB
YAML

# ============================================
# 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 deliberately gets only the *workspace* subtrees of
# this volume (the mount contract below) — never data/system or
# data/user/settings, which hold auth state and provider API keys.
- ./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), so the mounts are kept as
# narrow as the exec feature allows — workspace subtrees only. Secrets
# (data/system, data/user/settings) never enter this container; the
# residual exposure is cross-user visibility *between user workspaces*,
# acceptable for the invite-only trust posture and enforced no further.
volumes:
- ./data/user/workspace:/app/data/user/workspace
- ./data/users:/app/data/users
# 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