docker-compose.yml
Rama main · réplica de lectura del repositorio original.
# ============================================
# 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.
#
# Content workspace:
# DEEPTUTOR_WORKSPACE_HOST=/absolute/host/folder python scripts/docker_compose.py up -d
# maps that folder to the stable in-container path /workspace. If omitted,
# ./data/user/workspace remains the default.
# ============================================
services:
# Redis is internal-only runtime coordination state. AOF keeps unflushed
# event/command streams available across a sidecar restart; business data
# remains in SQLite/PocketBase.
redis:
image: redis:7.4-alpine
container_name: deeptutor-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec"]
volumes:
- ./data/redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
networks:
- deeptutor-network
# ============================================
# 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
- "${DEEPTUTOR_WORKSPACE_HOST:-./data/user/workspace}:/workspace"
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
- DEEPTUTOR_WORKSPACE_ROOT=/workspace
- DEEPTUTOR_WORKSPACE_ALLOWED_ROOTS=/workspace
# Optional dependencies, re-applied on every start (#762). Installing
# these with `docker exec` does not survive `compose down`, so declare
# them here instead. Both are idempotent and never fatal; the pip cache
# lives on the data volume, so a rebuild reuses what it already fetched.
# Extras come from pyproject.toml — preview one with
# `python scripts/install_extras.py --dry-run "<name>"`.
# - DEEPTUTOR_EXTRAS=math-animator,partners
# - DEEPTUTOR_APT_PACKAGES=ffmpeg
# Unraid / NAS bind mounts: set these to the host owner of ./data
# instead of chowning the volume or running the app as root.
# The entrypoint remaps the unprivileged `deeptutor` user to these ids.
# - PUID=99
# - PGID=100
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${DEEPTUTOR_DOCKER_BACKEND_PORT:-8001}/health/ready"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
redis:
condition: service_healthy
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 content workspace at the SAME stable path as the main app.
# The whole tree is readable so workspace_list/read and shell tools can use
# user files. Only outputs/ is writable: a command cannot modify source
# material, user settings, or any other host path. The runner never sees
# data/system, data/users, or the rest of the application data tree.
volumes:
- "${DEEPTUTOR_WORKSPACE_HOST:-./data/user/workspace}:/workspace:ro"
- "${DEEPTUTOR_WORKSPACE_HOST:-./data/user/workspace}/outputs:/workspace/outputs"
# 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
environment:
- DEEPTUTOR_RUNNER_ALLOWED_WORKDIRS=/workspace/outputs
# 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