Ctrl K
ComunidadGuías…

docker-compose.prod-core.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# ══════════════════════════════════════════════════════════════════════
#  Geny — Docker Compose (Production with Nginx / Core — 경량 버전)
#
#  TTS 로컬 엔진 없이 핵심 서비스만 실행합니다.
#  도메인을 연결하여 단일 포트(80)로 backend / frontend를 라우팅합니다.
#
#    /api/*            → backend:8000
#    /health           → backend:8000
#    /static/assets/*  → backend:8000
#    /*                → frontend:3000
#
#  Usage:
#    docker compose -f docker-compose.prod-core.yml up --build -d
#    docker compose -f docker-compose.prod-core.yml down
#    docker compose -f docker-compose.prod-core.yml logs -f
#
#  전체 버전 (TTS 포함):
#    docker compose -f docker-compose.prod.yml up --build -d
#
#  도메인 / SSL 설정:
#    DOMAIN=geny.example.com  → nginx server_name 에 반영
#    SSL 은 별도의 certbot 또는 Cloudflare 등으로 처리 후
#    nginx/nginx.conf 에 443 블록을 추가하세요.
#
#  Port 변경:
#    NGINX_PORT=58443 docker compose -f docker-compose.prod-core.yml up --build
# ══════════════════════════════════════════════════════════════════════

services:

  # ── PostgreSQL Database ───────────────────────────────────────────
  postgres:
    image: postgres:16-alpine
    container_name: geny-postgres-prod
    restart: unless-stopped
    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-prod:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-geny} -d ${POSTGRES_DB:-geny}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - geny-net-prod

  # ── Backend (FastAPI + LangGraph + Claude CLI) ────────────────────
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: geny-backend-prod
    restart: unless-stopped
    expose:
      - "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}
      # ── Persistent-storage paths (Phase 1, 2026-05-19) ─────────────
      # Mirrors docker-compose.prod.yml — each path is backed by a
      # named volume below. ``-prod`` suffix is shared with the full
      # prod compose so the two can swap freely without data loss.
      - 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
    volumes:
      - geny-tts-cache-prod:/app/cache/tts
      - geny-voices-prod:/app/static/voices
      # Chat attachment uploads (cycle 20260424_3 PR-1) — without a named
      # volume here every container restart wiped user-uploaded images.
      - geny-uploads-prod:/app/static/uploads
      # ── Backend storage root (notes / vault / capture logs) ────────
      - geny-agent-sessions-prod:/data/geny_agent_sessions
      # ── Phase-1 persistence volumes (2026-05-19) ───────────────────
      - geny-environments-prod:/data/environments
      - geny-trigger-presets-prod:/data/trigger_presets
      - geny-tool-presets-prod:/data/tool_presets
      - geny-chat-conversations-prod:/data/chat_conversations
      - geny-creature-state-prod:/data/creature_state
      - geny-config-variables-prod:/data/config_variables
      - geny-mcp-credentials-prod:/data/mcp
      # 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-prod:/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-prod}:/root/.claude:rw
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s
    networks:
      - geny-net-prod

  # ── 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
        - NEXT_PUBLIC_API_URL=
    container_name: geny-frontend-prod
    restart: unless-stopped
    expose:
      - "3000"
    environment:
      - API_URL=http://backend:8000
      - NEXT_PUBLIC_API_URL=
      - PORT=3000
      - HOSTNAME=0.0.0.0
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - geny-net-prod

  # ── Avatar Editor (geny-avatar submodule, Next.js) — Prod Core ────
  # Same shape as the prod-full entry: nginx in front, no host port,
  # NEXT_PUBLIC_BASE_PATH=/avatar-editor baked at build time so the
  # standalone bundle's routes/assets resolve under that prefix.
  avatar-editor:
    build:
      context: ./vendor/geny-avatar
      dockerfile: Dockerfile
      args:
        # NEXT_PUBLIC_* must be in build args (Next.js inlines them at
        # build time; runtime env is too late for the standalone bundle).
        NEXT_PUBLIC_BASE_PATH: /avatar-editor
        NEXT_PUBLIC_GENY_HOST: "true"
    container_name: geny-avatar-editor-prod
    init: true
    restart: unless-stopped
    expose:
      - "3000"
    environment:
      - NEXT_PUBLIC_BASE_PATH=/avatar-editor
      - NEXT_PUBLIC_GENY_HOST=true
      - GENY_BAKED_EXPORTS_DIR=/exports
      # AI provider keys for the editor's Generate panel. Empty
      # default → provider shows "<KEY> not set" in dropdown. Set in
      # .env to enable.
      - 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-prod:/exports
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3000/avatar-editor/ || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    networks:
      - geny-net-prod

  # ── Nginx Reverse Proxy ───────────────────────────────────────────
  nginx:
    image: nginx:1.27-alpine
    container_name: geny-nginx-prod
    restart: unless-stopped
    ports:
      - "${NGINX_PORT:-58443}:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      frontend:
        condition: service_started
      backend:
        condition: service_healthy
      avatar-editor:
        condition: service_started
    networks:
      - geny-net-prod

networks:
  geny-net-prod:
    driver: bridge

volumes:
  geny-pgdata-prod:
    driver: local
  geny-tts-cache-prod:
    driver: local
  geny-voices-prod:
    driver: local
  geny-uploads-prod:
    driver: local
  # Shared between avatar-editor (rw) and backend (ro) — prod variant.
  geny-baked-exports-prod:
    driver: local
  # Phase G — Claude Code OAuth credentials. See backend.volumes.
  geny-claude-creds-prod:
    driver: local
  # ── Backend storage root (notes / vault / capture logs) ────────────
  # Shares the ``-prod`` suffix with docker-compose.prod.yml so the
  # two compose files can swap freely without losing state.
  geny-agent-sessions-prod:
    driver: local
  # ── Phase-1 persistent-storage volumes (2026-05-19) ────────────────
  geny-environments-prod:
    driver: local
  geny-trigger-presets-prod:
    driver: local
  geny-tool-presets-prod:
    driver: local
  geny-chat-conversations-prod:
    driver: local
  geny-creature-state-prod:
    driver: local
  geny-config-variables-prod:
    driver: local
  geny-mcp-credentials-prod:
    driver: local