# DeerFlow Development Environment
# Usage: docker-compose -f docker-compose-dev.yaml up --build
#
# Services:
#   - nginx: Reverse proxy (port 2026)
#   - frontend: Frontend Next.js dev server (port 3000)
#   - gateway: Backend Gateway API + agent runtime (port 8001)
#   - provisioner (optional): Sandbox provisioner (creates Pods in host Kubernetes)
#
# Prerequisites:
#   - Kubernetes cluster + kubeconfig are only required when using provisioner mode.
#
# Access: http://localhost:2026

services:
  # ── Sandbox Provisioner ────────────────────────────────────────────────
  # Manages per-sandbox Pod + Service lifecycle in the host Kubernetes
  # cluster via the K8s API.
  # Backend accesses sandboxes directly via host.docker.internal:{NodePort}.
  provisioner:
    build:
      context: ./provisioner
      dockerfile: Dockerfile
      args:
        APT_MIRROR: ${APT_MIRROR:-}
    container_name: deer-flow-provisioner
    volumes:
      - ~/.kube/config:/root/.kube/config:ro
    environment:
      - K8S_NAMESPACE=deer-flow
      - SANDBOX_IMAGE=enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
      # Host paths for K8s HostPath volumes (must be absolute paths accessible by K8s node)
      # On Docker Desktop/OrbStack, use your actual host paths like /Users/username/...
      # Set these in your shell before running docker-compose:
      #   export DEER_FLOW_ROOT=/absolute/path/to/deer-flow
      - SKILLS_HOST_PATH=${DEER_FLOW_ROOT}/skills
      - THREADS_HOST_PATH=${DEER_FLOW_ROOT}/backend/.deer-flow/threads
      # Production: use PVC instead of hostPath to avoid data loss on node failure.
      # When set, hostPath vars above are ignored for the corresponding volume.
      # USERDATA_PVC_NAME uses subPath (deer-flow/users/{user_id}/threads/{thread_id}/user-data) automatically.
      # - SKILLS_PVC_NAME=deer-flow-skills-pvc
      # - USERDATA_PVC_NAME=deer-flow-userdata-pvc
      - KUBECONFIG_PATH=/root/.kube/config
      - NODE_HOST=host.docker.internal
      # Override K8S API server URL since kubeconfig uses 127.0.0.1
      # which is unreachable from inside the container
      - K8S_API_SERVER=https://host.docker.internal:26443
    env_file:
      - ../.env
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - deer-flow-dev
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
      interval: 10s
      timeout: 5s
      retries: 6
      start_period: 15s

  # ── Reverse Proxy ──────────────────────────────────────────────────────
  # Routes API traffic to gateway and (optionally) provisioner.
  nginx:
    image: nginx:alpine
    container_name: deer-flow-nginx
    ports:
      - "2026:2026"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf.template:ro
    command:
      - sh
      - -c
      - |
        set -e
        cp /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf
        test -e /proc/net/if_inet6 || sed -i '/^[[:space:]]*listen[[:space:]]\+\[::\]:2026;/d' /etc/nginx/nginx.conf
        exec nginx -g 'daemon off;'
    depends_on:
      - frontend
      - gateway
    networks:
      - deer-flow-dev
    restart: unless-stopped

  # Frontend - Next.js Development Server
  frontend:
    build:
      context: ../
      dockerfile: frontend/Dockerfile
      target: dev
      args:
        PNPM_STORE_PATH: ${PNPM_STORE_PATH:-/root/.local/share/pnpm/store}
        NPM_REGISTRY: ${NPM_REGISTRY:-}
    container_name: deer-flow-frontend
    command: sh -c "cd frontend && pnpm run dev > /app/logs/frontend.log 2>&1"
    volumes:
      - ../frontend/src:/app/frontend/src
      - ../frontend/public:/app/frontend/public
      - ../frontend/next.config.js:/app/frontend/next.config.js:ro
      - ../logs:/app/logs
      # Mount pnpm store for caching
      - ${PNPM_STORE_PATH:-~/.local/share/pnpm/store}:/root/.local/share/pnpm/store
    working_dir: /app
    environment:
      - NODE_ENV=development
      - WATCHPACK_POLLING=true
      - CI=true
      - DEER_FLOW_INTERNAL_GATEWAY_BASE_URL=http://gateway:8001
    env_file:
      - ../frontend/.env
    networks:
      - deer-flow-dev
    restart: unless-stopped

  # Backend - Gateway API
  gateway:
    build:
      context: ../
      dockerfile: backend/Dockerfile
      target: dev
      # cache_from disabled - requires manual setup: mkdir -p /tmp/docker-cache-gateway
      args:
        APT_MIRROR: ${APT_MIRROR:-}
        UV_IMAGE: ${UV_IMAGE:-ghcr.io/astral-sh/uv:0.7.20}
        UV_INDEX_URL: ${UV_INDEX_URL:-https://pypi.org/simple}
    container_name: deer-flow-gateway
    # Startup logic lives in docker/dev-entrypoint.sh — UV_EXTRAS validation,
    # `uv sync --all-packages`, .venv self-heal, and uvicorn handoff. Keeps
    # this file readable and lets the script be linted (shellcheck-clean).
    # See PR #2767 / Issue #2754.
    command: ["sh", "/usr/local/bin/dev-entrypoint.sh"]
    volumes:
      # Mount the dev entrypoint as a read-only file so edits to the script
      # take effect on `make docker-restart` without requiring an image rebuild.
      - ./dev-entrypoint.sh:/usr/local/bin/dev-entrypoint.sh:ro
      - ../backend/:/app/backend/
      # Preserve the .venv built during Docker image build — mounting the full backend/
      # directory above would otherwise shadow it with the (empty) host directory.
      - gateway-venv:/app/backend/.venv
      - ../config.yaml:/app/config.yaml
      - ../extensions_config.json:/app/extensions_config.json
      - ../skills:/app/skills
      - ../logs:/app/logs
      # Use a Docker-managed uv cache volume instead of a host bind mount.
      # On macOS/Docker Desktop, uv may fail to create symlinks inside shared
      # host directories, which causes startup-time `uv sync` to crash.
      - gateway-uv-cache:/root/.cache/uv
      # DooD: AioSandboxProvider runs inside the Gateway process.
      - /var/run/docker.sock:/var/run/docker.sock
      # CLI auth directories for auto-auth (Claude Code + Codex CLI)
      - type: bind
        source: ${HOME:?HOME must be set}/.claude
        target: /root/.claude
        read_only: true
        bind:
          create_host_path: true
      - type: bind
        source: ${HOME:?HOME must be set}/.codex
        target: /root/.codex
        read_only: true
        bind:
          create_host_path: true
    working_dir: /app
    environment:
      - CI=true
      - DEER_FLOW_PROJECT_ROOT=/app
      - DEER_FLOW_HOME=/app/backend/.deer-flow
      - DEER_FLOW_CHANNELS_LANGGRAPH_URL=${DEER_FLOW_CHANNELS_LANGGRAPH_URL:-http://gateway:8001/api}
      - DEER_FLOW_CHANNELS_GATEWAY_URL=${DEER_FLOW_CHANNELS_GATEWAY_URL:-http://gateway:8001}
      - DEER_FLOW_INTERNAL_AUTH_TOKEN=${DEER_FLOW_INTERNAL_AUTH_TOKEN:-}
      - DEER_FLOW_HOST_BASE_DIR=${DEER_FLOW_ROOT}/backend/.deer-flow
      - DEER_FLOW_HOST_SKILLS_PATH=${DEER_FLOW_ROOT}/skills
      - DEER_FLOW_SANDBOX_HOST=host.docker.internal
      # Proxy values (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) are inherited from ../.env via env_file.
      # Only NO_PROXY is declared here so internal service hostnames are always exempt from the proxy.
      - NO_PROXY=${NO_PROXY:-}${NO_PROXY:+,}localhost,127.0.0.1,::1,gateway,frontend,nginx,provisioner,host.docker.internal
      - no_proxy=${no_proxy:-}${no_proxy:+,}localhost,127.0.0.1,::1,gateway,frontend,nginx,provisioner,host.docker.internal
    env_file:
      - ../.env
    extra_hosts:
      # For Linux: map host.docker.internal to host gateway
      - "host.docker.internal:host-gateway"
    networks:
      - deer-flow-dev
    restart: unless-stopped

volumes:
  # Persist .venv across container restarts so dependencies installed during
  # image build are not shadowed by the host backend/ directory mount.
  gateway-venv:
  gateway-uv-cache:

networks:
  deer-flow-dev:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.200.0/24
