# 로컬 개발용 Docker Compose — R-23 통합 dev stack (Sprint 3-4). # # 사용법 # docker compose -f docker-compose.dev.yml up -d # docker compose -f docker-compose.dev.yml down -v # -v로 volume까지 정리 # # 무엇을 포함하나 / 무엇을 포함 안 하나 # # 포함: # - server (apps/server, Hono, tsx-watch) # - db (Postgres 17 — production과 메이저 버전 매칭, gotcha #16) # - mock-openai (R-3 E2E용 mock LLM, scripts/mock-openai-server.ts) # # 포함 안 함 (의도적): # - GoTrue + Kong + Supabase Studio — 로컬은 `supabase start` CLI가 # 표준 (CLAUDE.md "supabase start" 권장). dev compose에서 이 셋을 # 관리하면 supabase CLI와 두 가지 진실의 출처가 됨. CI에서도 # `supabase start`를 sidecar로 띄우는 게 supabase 팀이 유지하는 # 세팅 그대로라 깨질 일 적음. # - web (apps/web) — `pnpm --filter web dev`로 직접 실행. # Next.js dev server는 hot reload + RSC 변경 감지가 컨테이너 외부 # 실행이 훨씬 빠름. # # 환경 변수 (.env 또는 export) # 필수: SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_SERVICE_ROLE_KEY / # ENCRYPTION_KEY # ClickHouse(컨테이너): 아래 service 안에서 자동 설정 (외부 .env 불필요) # Mock OpenAI: 자동 (port 4000) version: '3.9' services: server: build: context: ./apps/server dockerfile: Dockerfile.dev ports: - '3001:3001' environment: PORT: 3001 SUPABASE_URL: ${SUPABASE_URL:-http://supabase-kong:8000} SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY} SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY} ENCRYPTION_KEY: ${ENCRYPTION_KEY} # 로컬 Postgres 직결. Supavisor는 로컬 스택에 없으므로 pooler 대신 # db service를 그대로 가리킨다 — 프로덕션은 포트 6543 transaction # 모드를 쓴다 (.env.example 참고). SUPABASE_DB_POOLER_URL: postgresql://postgres:postgres@db:5432/postgres # E2E 모드에서 mock OpenAI를 LLM upstream으로 사용. proxy가 실제 # OpenAI API에 도달하지 않게 하려면 OPENAI_API_BASE를 mock-openai # service URL로 override. 평상시 dev에선 unset 두면 진짜 OpenAI. OPENAI_API_BASE: ${OPENAI_API_BASE:-} volumes: - ./apps/server/src:/app/src restart: unless-stopped depends_on: db: condition: service_healthy # R-22: readiness probe — same endpoint Vercel preview / production # docker invocations use. /health/ready pings Postgres (both routes) + # Upstash (Upstash 'skipped' is OK), so a half-broken container shows # up as "unhealthy" before Docker routes traffic to it. start_period # gives node + tsx-watch time to compile on cold container start. # # Why `node -e` instead of curl/wget: node:22-alpine ships neither by # default, and adding an apk install just for healthcheck inflates the # image. node 22 has fetch built in, so this stays zero-dep. healthcheck: test: - 'CMD-SHELL' - "node -e \"fetch('http://localhost:3001/health/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"" interval: 30s timeout: 5s retries: 3 start_period: 30s db: # R-23: production은 Postgres 17 (gotcha #16). dev도 17로 맞춰서 # major-version skew로 인한 SQL/feature 차이 없게. supabase/postgres # 이미지는 extension까지 포함이라 supabase CLI 환경과 동일하지만 # 별도 dependency가 무거우므로 plain alpine으로 시작 — extension # 필요해지면 image만 supabase/postgres:17.x로 swap. image: postgres:17-alpine ports: - '54322:5432' environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U postgres'] interval: 5s timeout: 5s retries: 5 mock-openai: # R-3 E2E용 OpenAI-compatible mock. server의 OPENAI_API_BASE를 # http://mock-openai:4000/v1로 override하면 실제 OpenAI API에 비용 # 없이 E2E를 돌릴 수 있다. 평상시 dev에선 OPENAI_API_BASE를 비워두면 # 진짜 OpenAI로 갑니다 (이 컨테이너는 그래도 띄우지만 traffic이 안 옴). build: context: ./apps/server dockerfile: Dockerfile.dev command: ['pnpm', 'exec', 'tsx', 'scripts/mock-openai-server.ts'] ports: - '4000:4000' environment: MOCK_OPENAI_PORT: 4000 volumes: - ./apps/server/scripts:/app/scripts healthcheck: test: - 'CMD-SHELL' - "node -e \"fetch('http://localhost:4000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"" interval: 10s timeout: 5s retries: 5 start_period: 10s volumes: postgres_data: