# ══════════════════════════════════════════════════════════════════════ # Geny — Docker Compose (Development / Full) # # 소스 코드가 바인드 마운트되어 로컬 편집이 컨테이너에 즉시 반영됩니다. # 로컬 오디오 엔진 (TTS / STT) 은 --profile audio-local 로 활성화할 수 있습니다. # # - Backend : uvicorn --reload (watchfiles 기반 hot reload) # - Frontend: next dev (HMR / Fast Refresh) # # Usage: # docker compose -f docker-compose.dev.yml up --build # docker compose -f docker-compose.dev.yml --profile audio-local up --build # docker compose -f docker-compose.dev.yml down # docker compose -f docker-compose.dev.yml logs -f # # 경량 버전 (TTS 없음): # docker compose -f docker-compose.dev-core.yml up --build # # ══════════════════════════════════════════════════════════════════════ services: # ── PostgreSQL Database ─────────────────────────────────────────── postgres: image: postgres:16-alpine container_name: geny-postgres-dev init: true restart: unless-stopped ports: - "127.0.0.1:${POSTGRES_PORT:-5432}:5432" environment: POSTGRES_DB: ${POSTGRES_DB:-geny} POSTGRES_USER: ${POSTGRES_USER:-geny} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-geny123} POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" TZ: ${TIMEZONE:-Asia/Seoul} volumes: - geny-pgdata-dev:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-geny} -d ${POSTGRES_DB:-geny}"] interval: 30s timeout: 5s retries: 3 start_period: 10s networks: - geny-net-dev # ── Backend (FastAPI + LangGraph + Claude CLI) — Dev Mode ───────── backend: build: context: ./backend dockerfile: Dockerfile container_name: geny-backend-dev init: true restart: unless-stopped ports: - "127.0.0.1:${BACKEND_PORT:-8000}:8000" env_file: - path: ./backend/.env required: false environment: - APP_HOST=0.0.0.0 - APP_PORT=8000 - DEBUG_MODE=true - GENY_AGENT_STORAGE_ROOT=/data/geny_agent_sessions # PostgreSQL - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_DB=${POSTGRES_DB:-geny} - POSTGRES_USER=${POSTGRES_USER:-geny} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-geny123} - AUTO_MIGRATION=true - TIMEZONE=${TIMEZONE:-Asia/Seoul} # ── Persistent-storage paths (Phase 1, 2026-05-19) ───────────── # Mirrors docker-compose.prod.yml — each path is backed by a # named volume so its data survives ``docker compose up --build``. - ENVIRONMENT_STORAGE_PATH=${ENVIRONMENT_STORAGE_PATH:-/data/environments} - TRIGGER_PRESET_STORAGE_PATH=${TRIGGER_PRESET_STORAGE_PATH:-/data/trigger_presets} - GENY_TOOL_PRESETS_DIR=/data/tool_presets - GENY_CHAT_CONVERSATIONS_DIR=/data/chat_conversations - GENY_GAME_STATE_DB=/data/creature_state/geny_state.sqlite3 - GENY_CONFIG_VARIABLES_DIR=/data/config_variables - GENY_MCP_CREDENTIALS=/data/mcp/credentials.json - GENY_VOICE_STUDIO_DATA_DIR=/data/voice_studio volumes: # ── Bind mount: 로컬 소스 ↔ 컨테이너 동기화 ────────────────── - ./backend:/app # Persistent agent storage root — see the matching block in # docker-compose.prod.yml. Without this every ``docker compose # up --build backend`` wipes user notes / audio captures. - geny-agent-sessions-dev:/data/geny_agent_sessions # ── Phase-1 persistence volumes (2026-05-19) ──────────────────── # Mirrors docker-compose.prod.yml. Each env var declared above # resolves to a path that is backed by a named volume — without # these mounts ``docker compose up --build`` wipes the data. - geny-environments-dev:/data/environments - geny-trigger-presets-dev:/data/trigger_presets - geny-tool-presets-dev:/data/tool_presets - geny-chat-conversations-dev:/data/chat_conversations - geny-creature-state-dev:/data/creature_state - geny-config-variables-dev:/data/config_variables - geny-mcp-credentials-dev:/data/mcp - geny-voice-studio-dev:/data/voice_studio # Baked puppet zips dropped here by avatar-editor. Backend reads + moves to # installed/ subdir after install (rw — was :ro before C.3 added install). - geny-baked-exports-dev:/data/baked-imports # Phase G — host's ``~/.claude`` for Pro/Max OAuth credential # passthrough to the in-container ``claude`` CLI. RW for token # refresh. - ${CLAUDE_HOST_DIR:-geny-claude-creds-dev}:/root/.claude:rw depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:8000/health"] interval: 30s timeout: 5s retries: 3 start_period: 30s networks: - geny-net-dev # ── Frontend (Next.js) — Dev Mode ───────────────────────────────── frontend: build: # Root context: the page renders shared/chat, which lives beside # frontend/ and must be in the image. context: . dockerfile: frontend/Dockerfile.dev container_name: geny-frontend-dev init: true restart: unless-stopped ports: - "127.0.0.1:${FRONTEND_PORT:-3000}:3000" environment: - API_URL=http://backend:8000 - NEXT_PUBLIC_BACKEND_PORT=${BACKEND_PORT:-8000} - PORT=3000 - HOSTNAME=0.0.0.0 volumes: # ── Bind mount: 로컬 소스 ↔ 컨테이너 동기화 ────────────────── - ./frontend/src:/app/src - ./frontend/public:/app/public - ./frontend/next.config.ts:/app/next.config.ts - ./frontend/tsconfig.json:/app/tsconfig.json - ./frontend/postcss.config.mjs:/app/postcss.config.mjs depends_on: backend: condition: service_healthy networks: - geny-net-dev # ── OmniVoice (Optional · Dev Mode — server/ hot-reload) ───────── omnivoice: build: context: ./omnivoice dockerfile: Dockerfile image: geny-omnivoice:dev container_name: geny-omnivoice-dev profiles: ["audio-local"] init: true restart: unless-stopped ports: - "127.0.0.1:${OMNIVOICE_PORT:-9881}:9881" environment: - OMNIVOICE_MODEL=${OMNIVOICE_MODEL:-k2-fsa/OmniVoice} - OMNIVOICE_DEVICE=${OMNIVOICE_DEVICE:-cuda:0} - OMNIVOICE_DTYPE=${OMNIVOICE_DTYPE:-float16} # 5070(Blackwell, sm_120) 기준. 공유 GPU 환경에서는 1~2 로 내릴 것. - OMNIVOICE_MAX_CONCURRENCY=${OMNIVOICE_MAX_CONCURRENCY:-1} - OMNIVOICE_DEFAULT_NUM_STEP=${OMNIVOICE_DEFAULT_NUM_STEP:-14} - OMNIVOICE_GPU_MEMORY_FRACTION=${OMNIVOICE_GPU_MEMORY_FRACTION:-0.65} - PYTORCH_CUDA_ALLOC_CONF=${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True} - OMNIVOICE_AUTO_ASR=${OMNIVOICE_AUTO_ASR:-false} - OMNIVOICE_LOG_LEVEL=${OMNIVOICE_LOG_LEVEL:-debug} - HF_ENDPOINT=${HF_ENDPOINT:-https://huggingface.co} volumes: # ── Bind mount: server/ hot-reload 만 활성. omnivoice_core/ 는 빌드 시 고정. - ./omnivoice/server:/app/server - ./backend/static/voices:/voices:ro - geny-omnivoice-models-dev:/models command: - python - -m - uvicorn - server.main:app - --host - 0.0.0.0 - --port - "9881" - --reload - --reload-dir - /app/server shm_size: 8G deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:9881/health"] interval: 30s timeout: 10s retries: 3 start_period: 300s networks: - geny-net-dev # ── Avatar Editor (geny-avatar submodule, Next.js) — Dev Mode ───── # Same standalone build as the standalone-full compose (no hot-reload # of vendor/geny-avatar — it's a pinned submodule, not local source). # Container/volume names suffixed with -dev for parallel dev/prod runs. avatar-editor: build: context: ./vendor/geny-avatar dockerfile: Dockerfile args: # NEXT_PUBLIC_* must be passed at build time — see docker-compose.yml # comment for the full reasoning. NEXT_PUBLIC_BASE_PATH: "" NEXT_PUBLIC_GENY_HOST: "true" container_name: geny-avatar-editor-dev init: true restart: unless-stopped ports: - "127.0.0.1:${AVATAR_EDITOR_PORT:-3001}:3000" environment: - NEXT_PUBLIC_BASE_PATH= - NEXT_PUBLIC_GENY_HOST=true - GENY_BAKED_EXPORTS_DIR=/exports # AI provider keys for the editor's Generate panel. - GEMINI_API_KEY=${GEMINI_API_KEY:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - REPLICATE_API_TOKEN=${REPLICATE_API_TOKEN:-} - PORT=3000 - HOSTNAME=0.0.0.0 - NODE_ENV=production volumes: - geny-baked-exports-dev:/exports healthcheck: test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3000/ || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 30s networks: - geny-net-dev # ── Whisper STT (dev variant of the prod service) ─────────────── # GPU split (RTX 5070, 12 GB): # omnivoice → 0.50 (≈ 6 GB) # whisper → 0.35 (≈ 4 GB) # total 0.85 (~10 GB) — 15% headroom for stability whisper-stt: build: context: ./whisper-stt dockerfile: Dockerfile image: geny-whisper-stt:dev container_name: geny-whisper-stt-dev profiles: ["audio-local"] init: true restart: unless-stopped expose: - "8001" environment: - WHISPER_MODEL=${WHISPER_MODEL:-openai/whisper-large-v3} - WHISPER_PORT=${WHISPER_PORT:-8001} - WHISPER_GPU_MEMORY_FRACTION=${WHISPER_GPU_MEMORY_FRACTION:-0.35} - WHISPER_DTYPE=${WHISPER_DTYPE:-auto} # Blackwell (sm_120) FA2 incompat — see prod variant comment. - WHISPER_ATTENTION_BACKEND=${WHISPER_ATTENTION_BACKEND:-TRITON_ATTN} - HUGGINGFACE_HUB_TOKEN=${HUGGINGFACE_HUB_TOKEN:-} - HF_ENDPOINT=${HF_ENDPOINT:-https://huggingface.co} volumes: - geny-whisper-cache-dev:/cache/huggingface shm_size: 4G deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:8001/health"] interval: 30s timeout: 10s retries: 5 start_period: 600s networks: - geny-net-dev networks: geny-net-dev: driver: bridge volumes: geny-pgdata-dev: driver: local geny-omnivoice-models-dev: driver: local geny-whisper-cache-dev: driver: local # Backend storage root — see matching block in docker-compose.prod.yml. geny-agent-sessions-dev: driver: local # Shared between avatar-editor (rw) and backend (ro) — dev variant. geny-baked-exports-dev: driver: local # Phase G — Claude Code OAuth credentials. See backend.volumes. geny-claude-creds-dev: driver: local # ── Phase-1 persistent-storage volumes (2026-05-19) ───────────────── # See docker-compose.prod.yml for the full rationale. geny-environments-dev: driver: local geny-trigger-presets-dev: driver: local geny-tool-presets-dev: driver: local geny-chat-conversations-dev: driver: local geny-creature-state-dev: driver: local geny-config-variables-dev: driver: local geny-mcp-credentials-dev: geny-voice-studio-dev: driver: local