# 2dph — docker composition # # bin/stack/start / start-assistant / status / stop # docker compose up -d brain # API (Zig CGO serve) # docker compose --profile index run --rm index # Python rebuild # docker compose --profile picoclaw up -d # brain-mcp + CPU reasoner + PicoClaw gateway # docker compose --profile reasoner up -d reasoner # CPU Ollama :11435 # docker compose --profile searxng up -d # OCR_ENGINE=paddle docker compose --profile ocr-paddle run --rm ocr-paddle # # Secrets never baked in: search.env + db-profiles.yml from ~/.config/brain. name: 2dph networks: default: name: 2dph_sys driver: bridge ipam: config: - subnet: 10.23.42.0/24 services: brain: image: ghcr.io/eslider/2dph:api build: context: . dockerfile: Dockerfile target: api cache_from: - ghcr.io/eslider/2dph:cache command: ["serve"] environment: &env HF_HOME: /data/hf BRAIN_SEARCH_CACHE: /data/cache/web-search.sqlite BRAIN_DB_PROFILES: /secret/db-profiles.yml BRAIN_SEARCH_ENV: /secret/search.env KB_ROOT: /data KB_WORKERS: "4" KB_PORT: "8630" volumes: - kb-model:/data/hf - kb-var:/data - ~/.config/brain:/secret:ro ports: - "127.0.0.1:8630:8630" read_only: true tmpfs: - /tmp healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8630/health"] interval: 30s timeout: 5s retries: 3 restart: unless-stopped stop_grace_period: 20s brain-watch: image: ghcr.io/eslider/2dph:api environment: *env volumes: - kb-model:/data/hf - kb-var:/data - ..:/corpus:ro - ~/.config/brain:/secret:ro command: ["watch", "/corpus"] read_only: true tmpfs: - /tmp restart: unless-stopped stop_grace_period: 20s # Python write path (Ladybug rebuild). Not in the API image. # docker compose --profile index run --rm index index: profiles: ["index"] image: ghcr.io/eslider/2dph:index build: context: . dockerfile: Dockerfile target: index environment: HF_HOME: /data/hf KB_PY: python3 volumes: - kb-model:/data/hf - kb-var:/app/var - ..:/corpus:ro - ~/.config/brain:/secret:ro command: ["index"] read_only: true tmpfs: - /tmp # mail-sync: pull M365 (and OnlyOffice/Gmail) mail every 10 s into the shared # var volume, then rebuild the brain index. Runs on the index image so it can # convert + index in-process. Needs ~/.config/brain/m365.env for the M365 # source; override MAIL_SYNC_SRC / MAIL_SYNC_ENV for other providers. # docker compose up -d mail-sync mail-sync: image: ghcr.io/eslider/2dph:index build: context: . dockerfile: Dockerfile target: index environment: HF_HOME: /data/hf KB_PY: python3 MAIL_SYNC_SRC: m365 MAIL_SYNC_ENV: /secret/m365.env volumes: - kb-model:/data/hf - kb-var:/app/var - ~/.config/brain:/secret:ro command: ["mail-sync", "10"] read_only: true tmpfs: - /tmp restart: unless-stopped stop_grace_period: 20s # Optional local SearXNG (D3). Skip if BRAIN_SEARCH_URL already points at a # live instance — do not run a second copy on that host. # SEARXNG_SECRET=$(openssl rand -hex 32) docker compose --profile searxng up -d searxng: profiles: ["searxng"] image: docker.io/searxng/searxng:2026.8.10-0a118066d ports: - "127.0.0.1:8888:8080" environment: SEARXNG_SECRET: ${SEARXNG_SECRET:-} volumes: - ./deploy/searxng/settings.yml:/etc/searxng/settings.yml:ro - ./deploy/searxng/limiter.toml:/etc/searxng/limiter.toml:ro restart: unless-stopped # MCP endpoint for PicoClaw (and any MCP client). # docker compose --profile picoclaw up -d brain-mcp: profiles: ["picoclaw"] image: ghcr.io/eslider/2dph:api environment: *env volumes: - kb-model:/data/hf - kb-var:/data - ~/.config/brain:/secret:ro command: ["serve"] ports: - "127.0.0.1:8630:8630" read_only: true tmpfs: - /tmp restart: unless-stopped # CPU OpenAI-compatible sidecar (D18). Weights are pulled at runtime, not # baked into the 2dph image. Does not touch host Ollama on :11434. # docker compose --profile reasoner up -d reasoner # docker compose --profile reasoner exec reasoner ollama pull qwen3.5:9b reasoner: profiles: ["reasoner", "picoclaw"] image: docker.io/ollama/ollama:latest environment: OLLAMA_NUM_GPU: "0" OLLAMA_HOST: "0.0.0.0:11434" ports: - "127.0.0.1:11435:11434" volumes: - reasoner-ollama:/root/.ollama restart: unless-stopped # Official PicoClaw gateway. Config has no secrets (Ollama + HTTP MCP). # Host network: brain/reasoner bind 127.0.0.1 only, so host.docker.internal # (docker0) cannot reach them. Gateway 127.0.0.1:18790 (not the 18800 launcher). # If :8630/:11435 are already bound, do not start brain-mcp/reasoner: # docker compose --profile picoclaw up -d --no-deps picoclaw picoclaw: profiles: ["picoclaw"] image: docker.io/sipeed/picoclaw:v0.3.1 network_mode: host depends_on: - brain-mcp - reasoner environment: PICOCLAW_GATEWAY_HOST: "127.0.0.1" entrypoint: ["picoclaw", "gateway"] volumes: - picoclaw-home:/root/.picoclaw - ./deploy/picoclaw/config.json:/root/.picoclaw/config.json:ro restart: unless-stopped # Optional PP-OCRv5 (not default). Default OCR is tesseract eng+deu. # OCR_ENGINE=paddle docker compose --profile ocr-paddle run --rm ocr-paddle ocr-paddle: profiles: ["ocr-paddle"] image: python:3.12-slim environment: OCR_ENGINE: paddle command: ["python", "-c", "print('OCR_ENGINE=paddle; install paddleocr on PATH')"] volumes: kb-model: kb-var: reasoner-ollama: picoclaw-home: