docker-compose.prod-core.yml
Rama main · réplica de lectura del repositorio original.
# ══════════════════════════════════════════════════════════════════════
# 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