docker-compose.yml
Rama main · réplica de lectura del repositorio original.
# ══════════════════════════════════════════════════════════════════════
# Geny — Docker Compose (Standalone / Full)
#
# 전체 서비스 포함 (로컬 오디오 엔진 TTS/STT 는 --profile audio-local 로 활성화)
#
# Usage:
# docker compose up --build # 기본 실행 (TTS 미포함)
# docker compose up -d # 백그라운드 실행
# docker compose --profile audio-local up # 로컬 오디오 엔진 (TTS + STT) 포함
# docker compose down # 종료
# docker compose logs -f # 로그 확인
#
# Port 변경:
# BACKEND_PORT=8080 FRONTEND_PORT=3001 docker compose up --build
#
# 또는 .env 파일로 설정:
# BACKEND_PORT=8080
# FRONTEND_PORT=3001
#
# ══════════════════════════════════════════════════════════════════════
services:
# ── PostgreSQL Database ───────────────────────────────────────────
postgres:
image: postgres:16-alpine
container_name: geny-postgres
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:/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
# ── Backend (FastAPI + LangGraph + Claude CLI) ────────────────────
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: geny-backend
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
- 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}
# Inbound chat gateway (geny-executor >=2.11.0). Set the token in .env
# to let users DM the VTuber from Telegram; empty = off.
- GATEWAY_TELEGRAM_BOT_TOKEN=${GATEWAY_TELEGRAM_BOT_TOKEN:-}
- GATEWAY_TELEGRAM_ALLOWED_CHAT_IDS=${GATEWAY_TELEGRAM_ALLOWED_CHAT_IDS:-}
- GATEWAY_DISCORD_BOT_TOKEN=${GATEWAY_DISCORD_BOT_TOKEN:-}
- GATEWAY_DISCORD_ALLOWED_CHANNEL_IDS=${GATEWAY_DISCORD_ALLOWED_CHANNEL_IDS:-}
- GATEWAY_SLACK_APP_TOKEN=${GATEWAY_SLACK_APP_TOKEN:-}
- GATEWAY_SLACK_BOT_TOKEN=${GATEWAY_SLACK_BOT_TOKEN:-}
- GATEWAY_SLACK_ALLOWED_CHANNEL_IDS=${GATEWAY_SLACK_ALLOWED_CHANNEL_IDS:-}
# ── 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:
- geny-tts-cache:/app/cache/tts
- geny-voices:/app/static/voices
# ── Backend storage root (notes / vault / capture logs) ────────
- geny-agent-sessions:/data/geny_agent_sessions
# ── Phase-1 persistence volumes (2026-05-19) ───────────────────
- geny-environments:/data/environments
- geny-trigger-presets:/data/trigger_presets
- geny-tool-presets:/data/tool_presets
- geny-chat-conversations:/data/chat_conversations
- geny-creature-state:/data/creature_state
- geny-config-variables:/data/config_variables
- geny-mcp-credentials:/data/mcp
- geny-voice-studio:/data/voice_studio
# Chat attachment uploads (content-addressable: /app/static/uploads/{shard}/{sha256}.{ext}).
# Cycle 20260424_3 PR-1: without this volume the folder was ephemeral —
# every container restart wiped user-uploaded images.
- geny-uploads:/app/static/uploads
# Baked puppet zips dropped here by the avatar-editor service. Backend
# lists pending zips and "installs" them into static/{live2d,spine}-models/,
# then moves the consumed zip into installed/ — rw mount required.
# See docs/plan/GENY_AVATAR_INTEGRATION.md (Phase C).
- geny-baked-exports:/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}:/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: 15s
networks:
- geny-net
# ── Frontend (Next.js) ────────────────────────────────────────────
frontend:
build:
# Root context: the page renders shared/chat, which lives beside
# frontend/ and must be in the image.
context: .
dockerfile: frontend/Dockerfile
args:
- API_URL=http://backend:8000
container_name: geny-frontend
init: true
restart: unless-stopped
ports:
- "127.0.0.1:${FRONTEND_PORT:-3000}:3000"
environment:
- API_URL=http://backend:8000
- PORT=3000
- HOSTNAME=0.0.0.0
depends_on:
backend:
condition: service_healthy
networks:
- geny-net
# ── OmniVoice (Optional — Geny 자체 TTS 마이크로서비스, GPU 필요) ─
# geny-monorepo 내 omnivoice/ 에서 빌드. 포트 9881, voices 공유 볼륨,
# HF 모델 캐시는 별도 영구 볼륨. profile: audio-local 로 활성화.
omnivoice:
build:
context: ./omnivoice
dockerfile: Dockerfile
image: geny-omnivoice:latest
container_name: geny-omnivoice
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:-4}
- OMNIVOICE_DEFAULT_NUM_STEP=${OMNIVOICE_DEFAULT_NUM_STEP:-16}
- OMNIVOICE_GPU_MEMORY_FRACTION=${OMNIVOICE_GPU_MEMORY_FRACTION:-0.85}
- PYTORCH_CUDA_ALLOC_CONF=${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True}
- OMNIVOICE_AUTO_ASR=${OMNIVOICE_AUTO_ASR:-false}
- OMNIVOICE_LOG_LEVEL=${OMNIVOICE_LOG_LEVEL:-info}
- HF_ENDPOINT=${HF_ENDPOINT:-https://huggingface.co}
volumes:
- geny-voices:/voices:ro
- geny-omnivoice-models:/models
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
# ── Avatar Editor (geny-avatar submodule, Next.js) ────────────────
# Standalone Next.js app from vendor/geny-avatar (pinned to v0.2.0).
# Lets users edit Spine/Cubism puppets in-browser and "Send to Geny"
# baked model zips, which land in the geny-baked-exports volume the
# backend reads from. Standalone compose has no nginx in front, so
# the editor binds directly to a host port (default 3001).
#
# Build context is the submodule path — fresh clones must run
# `git submodule update --init --recursive` first or the build fails.
avatar-editor:
build:
context: ./vendor/geny-avatar
dockerfile: Dockerfile
args:
# NEXT_PUBLIC_* are inlined at build time; runtime env below is
# too late. basePath stays empty (root mount, no nginx); GENY_HOST
# enables the "send to Geny" button + /api/send-to-geny route.
NEXT_PUBLIC_BASE_PATH: ""
NEXT_PUBLIC_GENY_HOST: "true"
container_name: geny-avatar-editor
init: true
restart: unless-stopped
ports:
- "127.0.0.1:${AVATAR_EDITOR_PORT:-3001}:3000"
environment:
# Standalone compose has no reverse proxy → avatar-editor served
# at root, so basePath stays empty (Next.js falls back to "/" routes).
- NEXT_PUBLIC_BASE_PATH=
# Enables Geny library auto-sync (lib/sync/genySync) + the
# /api/library/sync proxy route. Browser-visible flag.
- 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:
# Write side of the shared baked-zip handoff (backend mounts the
# same volume read-only at /data/baked-imports).
- geny-baked-exports:/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
networks:
geny-net:
driver: bridge
volumes:
geny-pgdata:
driver: local
geny-tts-cache:
driver: local
geny-voices:
driver: local
geny-uploads:
driver: local
geny-omnivoice-models:
driver: local
# Shared between avatar-editor (rw at /exports) and backend (ro at
# /data/baked-imports). Carries baked puppet zips from the editor's
# "send to Geny" action to the backend's import flow.
geny-baked-exports:
driver: local
# Phase G — Claude Code OAuth credentials. See backend.volumes.
geny-claude-creds:
driver: local
# ── Backend storage root (notes / vault / capture logs) ───────────
geny-agent-sessions:
driver: local
# ── Phase-1 persistent-storage volumes (2026-05-19) ───────────────
geny-environments:
driver: local
geny-trigger-presets:
driver: local
geny-tool-presets:
driver: local
geny-chat-conversations:
driver: local
geny-creature-state:
driver: local
geny-config-variables:
driver: local
geny-mcp-credentials:
geny-voice-studio:
driver: local