diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml index 9130ab1..bd3b845 100644 --- a/deploy/docker-compose.prod.yml +++ b/deploy/docker-compose.prod.yml @@ -50,9 +50,31 @@ services: TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-} # 멘토 최초 비밀번호 (첫 부팅 시드용). 비우면 랜덤 생성돼 로그에 1회 출력된다. MENTOR_INITIAL_PASSWORD: ${MENTOR_INITIAL_PASSWORD:-} + # 멘토 피드백 AI 초안 — 아래 ollama-proxy 경유로 워크스테이션(tailnet)의 Ollama를 쓴다. + # 학습 포인트: @Value("${llm.base-url:}")는 환경변수 relaxed binding이 약해, LLM_BASE_URL을 + # 줘도 안 읽히는 경우가 있다. JVM 시스템 프로퍼티(-D)로 주면 확실하다. + # 이 값이 없으면 초안 기능만 조용히 꺼지고 앱은 정상 동작한다. + JAVA_TOOL_OPTIONS: "-Dllm.base-url=http://ollama-proxy:11434" depends_on: postgres: condition: service_healthy + ollama-proxy: + condition: service_started + restart: unless-stopped + + # 로컬 LLM 프록시 — 멘토 피드백 AI 초안이 워크스테이션(tailnet)의 Ollama를 쓰게 한다. + # 왜 프록시가 필요한가: Ollama는 DNS-rebinding 방어로 요청 Host 헤더를 검증해서, + # tailnet 이름(awesomedev.taildc21b5.ts.net)으로 오는 요청을 403으로 막는다. + # 이 프록시가 Host를 localhost로 재작성해 통과시킨다(Caddyfile 참조). + # 워크스테이션 쪽은 `tailscale serve`로 Ollama를 tailnet 전용 노출한다(Ollama는 127.0.0.1 유지). + ollama-proxy: + image: caddy:2-alpine + container_name: ollama-proxy + logging: *logging + extra_hosts: + - "awesomedev.taildc21b5.ts.net:100.67.10.124" # 워크스테이션 tailnet IP + volumes: + - ./ollama-proxy/Caddyfile:/etc/caddy/Caddyfile:ro restart: unless-stopped # 프론트 빌드 결과물을 공유 볼륨(webroot)에 복사해 주고 종료하는 일회성 컨테이너 diff --git a/deploy/ollama-proxy/Caddyfile b/deploy/ollama-proxy/Caddyfile new file mode 100644 index 0000000..a1608a6 --- /dev/null +++ b/deploy/ollama-proxy/Caddyfile @@ -0,0 +1,9 @@ +# Ollama 앞단 Host 재작성 프록시. +# Ollama는 DNS-rebinding 방어로 요청 Host를 검증해, tailnet 이름으로 오는 요청을 403으로 막는다. +# 여기서 Host를 localhost로 바꿔 통과시킨다. 업스트림은 워크스테이션의 `tailscale serve`(HTTPS, :8443). +# SNI/인증서는 실제 이름(awesomedev.taildc21b5.ts.net)으로 맺고, HTTP Host 헤더만 localhost로 보낸다. +:11434 { + reverse_proxy https://awesomedev.taildc21b5.ts.net:8443 { + header_up Host localhost:11434 + } +}