Skip to content

feat: Docker 셀프호스팅 전체 스택 구현#61

Open
hyoda wants to merge 2 commits into
opentutorials-org:mainfrom
hyoda:feat/docker-self-hosting-v2
Open

feat: Docker 셀프호스팅 전체 스택 구현#61
hyoda wants to merge 2 commits into
opentutorials-org:mainfrom
hyoda:feat/docker-self-hosting-v2

Conversation

@hyoda

@hyoda hyoda commented Feb 1, 2026

Copy link
Copy Markdown

Summary

Docker Compose 기반으로 OTU 전체 스택(Next.js + Supabase 7개 서비스 + Caddy)을 셀프호스팅할 수 있는 인프라를 구현합니다.

  • 신규 파일 23개 (Docker 인프라, CLI, 문서, CI/CD)
  • 수정 파일 8개 (AI Provider 추상화, Vercel stub, 설정 변경)

PR #31 2차 리뷰 피드백 반영 현황

이 PR은 PR #31의 2차 리뷰 피드백(Critical 3건 + Important 6건 + Suggestion 5건)을 모두 반영하여 upstream/main 최신 코드 기반으로 재구현한 것입니다.

Critical (3건 — 모두 반영)

ID 내용 반영
C1 DB_ENC_KEY 하드코딩 제거 openssl rand -hex 8로 AES-128 호환 16바이트 랜덤 생성. docker-compose.yml, generate-keys.sh, otu.sh 모두 환경변수 참조로 변경
C2 MIGRATION_FAIL_FAST 옵션 docker/entrypoint.sh에서 MIGRATION_FAIL_FAST=true 시 마이그레이션 실패 카운터 > 0이면 exit 1으로 앱 시작 차단
C3 Kong entrypoint 검증 템플릿 파일 존재 여부 + ANON_KEY/SERVICE_ROLE_KEY 빈값 검증 추가

Important (6건 — 4건 반영, 2건 기반영/범위 밖)

ID 내용 반영
I4 환경변수 치환 시 파일 없으면 경고 /app/.next 디렉토리 존재 확인 후 WARNING 로그 출력
I5 FILENAME SQL injection 방지 psql -v version="$VERSION" -v name="$FILENAME" 파라미터 바인딩 사용
I6 runtime 'nodejs' 변경 의도 문서화 셀프호스팅 환경 호환성 설명 주석 추가
I7 동적 require 패턴 설명 → stub 방식으로 전환하여 해결 (아래 상세 설명)
I8 Sentry 대안 PR 범위 밖 — upstream에서 이미 Sentry 제거 완료 (#33)
I9 반환 타입 변경 이미 수정 완료 — 모든 호출부 null 처리 확인됨

Suggestion (5건 — 모두 반영)

ID 내용 반영
S10 Alpine 태그 고정 node:20-alpine3.21 사용
S11 sudo/HOME 경로 주석 install-remote.sh 상단에 주의사항 추가
S12 WatermelonDB 마이그레이션 WatermelonDB 자동 처리 — 변경 불필요
S13 RFC 6761 주석 수정 .env.example에서 "브라우저 구현에 따라 다를 수 있으며" 문구 보완
S14 pgroonga 주석 "일본어 전문검색용" → "다국어 전문검색용" 변경

핵심 설계 결정: Vercel 의존성 처리 (stub 방식)

문제

upstream 코드의 middleware.tssupabase/utils/middleware.ts에서 @vercel/flags(reportValue)와 @vercel/functions(ipAddress)를 static import하고 있습니다. 셀프호스팅(Docker) 환경에서는 이 패키지가 정상 동작하지 않습니다.

검토한 접근법

방식 설명 채택 여부
1. dynamic require require() + try/catch로 MODULE_NOT_FOUND 처리 ❌ — Vercel 배포 시 tree-shaking/Edge Runtime 호환성 문제
2. 환경변수 분기 import는 유지하되 호출 시점에서 분기 ❌ — import 자체가 실패하면 의미 없음
3. Docker 빌드 시 stub 주입 JS 구현만 no-op으로 교체, .d.ts 유지 ✅ 채택

채택한 방식 상세

# Dockerfile builder 단계
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# npm ci로 설치된 실제 @vercel/* 패키지의 JS 파일만 stub으로 덮어씀
RUN cp docker/stubs/@vercel/flags/dist/index.js  node_modules/@vercel/flags/dist/index.js && \
    cp docker/stubs/@vercel/flags/dist/index.cjs node_modules/@vercel/flags/dist/index.cjs && \
    cp docker/stubs/@vercel/functions/index.js    node_modules/@vercel/functions/index.js

장점:

  • middleware.ts, supabase/utils/middleware.tsupstream/main과 100% 동일 → 머지 충돌 없음
  • .d.ts 타입 선언은 원본 유지 → TypeScript 빌드 보장
  • Vercel 배포에 영향 없음 (Dockerfile을 사용하지 않으므로)

배포 테스트 결과

otu.hyoda.kr(AWS Lightsail, Ubuntu 22.04)에서 실제 배포 테스트를 수행했습니다.

서비스 상태

서비스 컨테이너 상태
Next.js App otu-app-1 ✅ healthy
PostgreSQL + pgvector + pgroonga otu-db-1 ✅ healthy
Kong API Gateway otu-kong-1 ✅ healthy
GoTrue (Auth) otu-auth-1 ✅ running
PostgREST (REST) otu-rest-1 ✅ running
Realtime (WebSocket) otu-realtime-1 ✅ running
Storage otu-storage-1 ✅ running

테스트 중 발견/수정한 이슈

이슈 원인 수정
Kong 시작 실패 entrypoint.sh git 실행 권한 누락 chmod +x 반영
앱 반복 재시작 set -e + $() 서브쉘에서 psql 실패 시 즉시 종료 && RC=0 || RC=$? 패턴으로 안전 캡처
Realtime Bad key size DB_ENC_KEY 32바이트 (AES-128은 16바이트 필요) openssl rand -hex 8로 변경
Docker healthcheck 실패 Alpine wget이 localhost를 IPv6(::1)로 해석 127.0.0.1 사용

영향 범위 및 머지 시 고려사항

⚠️ 이 PR은 충분한 검토 후 머지해 주시기 바랍니다.

Vercel 배포 영향

  • middleware.ts / supabase/utils/middleware.ts: upstream과 동일 → 영향 없음
  • AI 라우트 (askLLM, captioning, titling, similaritySearch): getAIProvider() 분기 추가. AI_PROVIDER 환경변수가 없으면 기본값 'gateway'로 기존 동작과 동일
  • next.config.js: output: 'standalone' 추가 — Vercel은 이 설정을 무시하고 자체 빌드 파이프라인을 사용하므로 동작에 영향 없음. 단, Vercel 팀에서 이 설정이 의도하지 않은 동작을 유발하는지 확인 필요
  • src/functions/ai/config.ts: AIProvider 타입과 getAIProvider(), canUseAI(), getAIDisabledReason() 등 새 함수 추가. 기존 함수 시그니처 변경 없음

반드시 확인

  1. Vercel 프리뷰 빌드 성공 여부output: 'standalone'이 Vercel 빌드에 영향을 주는지
  2. AI 기능 정상 동작AI_PROVIDER 환경변수 미설정 시 기존 gateway 모드로 fallback되는지
  3. @vercel/flags deprecated 경고 — npm warn에서 @vercel/flags@2.6.3flags로 rename 권고. 별도 이슈로 대응 필요

가급적 확인

  1. Docker 로컬 빌드 테스트 (docker compose --env-file docker/.env config)
  2. otu.sh install 로컬 모드 정상 동작

참고

  1. upstream @lingui/swc-plugin 미설치로 인한 테스트 실패는 기존 이슈 (이 PR과 무관)
  2. PERSONAL_SAAS_GUIDE.md는 이 PR에 포함되지 않음

파일 목록

신규 (23개)

Docker 인프라
  • .dockerignore — 빌드 제외 패턴
  • Dockerfile — 3단계 멀티스테이지 빌드
  • docker-compose.yml — 개발/소스 빌드 구성
  • docker-compose.prod.yml — GHCR 사전 빌드 이미지 구성
  • docker/.env.example — 환경변수 템플릿
  • docker/caddy/Caddyfile — 리버스 프록시 (자동 HTTPS)
  • docker/db/Dockerfile — PostgreSQL + pgvector + pgroonga
  • docker/db/init-extensions.sql — DB 확장 초기화
  • docker/db/init-roles.sh — Supabase 역할 비밀번호 설정
  • docker/entrypoint.sh — 앱 시작 (마이그레이션 + seed + 환경변수 치환)
  • docker/generate-keys.sh — 보안 키 생성 스크립트
  • docker/kong/entrypoint.sh — Kong 환경변수 치환 + 검증
  • docker/kong/kong.yml — Kong 선언적 라우팅 설정
  • docker/lib-jwt.sh — JWT 생성 공통 라이브러리
  • docker/stubs/@vercel/flags/dist/index.js — Vercel flags ESM stub
  • docker/stubs/@vercel/flags/dist/index.cjs — Vercel flags CJS stub
  • docker/stubs/@vercel/functions/index.js — Vercel functions CJS stub
  • docker/volumes/db/init/00-init-extensions.sql — 볼륨 마운트용 초기화 SQL
CLI, 문서, CI/CD
  • otu.sh — 셀프호스팅 CLI (install/start/stop/upgrade/backup)
  • install-remote.sh — 원클릭 설치 스크립트
  • docs/self-hosting.md — 셀프호스팅 가이드
  • .github/workflows/docker-publish.yml — GHCR 이미지 빌드/배포

수정 (8개)

파일 변경 내용
.gitignore docker/.env, backups/ 추가
next.config.js output: 'standalone' 추가
src/functions/ai/config.ts AIProvider 타입, getAIProvider() 함수 추가
src/functions/ai.js provider별 임베딩 모델 선택
app/api/ai/askLLM/openai/route.tsx runtime nodejs + provider 분기
app/api/ai/captioning/route.ts provider 분기
app/api/ai/titling/route.ts provider 분기
app/api/ai/similaritySearch/route.tsx runtime nodejs + 의도 주석

Test plan

  • otu.hyoda.kr 실배포 테스트 완료 (7개 서비스 전체 healthy)
  • Vercel 프리뷰 빌드 성공 확인
  • AI 기능 정상 동작 확인 (AI_PROVIDER 미설정 시 gateway fallback)
  • Docker 로컬 빌드 테스트 (bash otu.sh install 로컬 모드)

🤖 Generated with Claude Code

Closes #30

devwinery and others added 2 commits February 1, 2026 09:09
Docker Compose 기반으로 OTU 전체 스택(Next.js + Supabase 7개 서비스)을
셀프호스팅할 수 있는 인프라를 구현한다.

주요 구성:
- 3단계 멀티스테이지 Dockerfile (deps → builder → runner)
- docker-compose.yml (소스 빌드), docker-compose.prod.yml (사전 빌드 이미지)
- Caddy 리버스 프록시 (자동 HTTPS via Let's Encrypt)
- Kong API Gateway (auth/rest/realtime/storage 라우팅)
- otu.sh CLI (install/start/stop/upgrade/backup)
- install-remote.sh 원클릭 설치 스크립트
- docker/entrypoint.sh 멱등 마이그레이션 + seed + 런타임 환경변수 치환
- GHCR 이미지 빌드/배포 CI/CD 워크플로우

보안:
- DB_ENC_KEY: AES-128 호환 16바이트 랜덤 생성 (openssl rand -hex 8)
- Kong entrypoint: 템플릿 파일 존재 + ANON_KEY/SERVICE_ROLE_KEY 빈값 검증
- 마이그레이션 VERSION 숫자 검증, FILENAME psql -v 파라미터 바인딩 (SQL injection 방지)
- MIGRATION_FAIL_FAST 옵션으로 마이그레이션 실패 시 앱 시작 차단

빌드 안정성:
- node:20-alpine3.21 태그 고정 (재현 가능한 빌드)
- Docker healthcheck: 127.0.0.1 사용 (Alpine wget IPv6 해석 문제 방지)
- entrypoint.sh set -e 환경에서 psql 실패 시 exit code 안전 캡처

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
셀프호스팅 환경에서 앱 코드가 동작하도록 AI Provider 분기와
Vercel 전용 모듈 처리를 추가한다.

AI Provider 추상화:
- AIProvider 타입 ('gateway' | 'openai') 및 getAIProvider() 함수 추가
- askLLM, captioning, titling 라우트에서 provider별 모델 분기
- src/functions/ai.js 임베딩에서 provider별 모델 선택
- runtime 'nodejs' 변경 (셀프호스팅 환경 Node.js 모듈 호환)

Vercel 전용 모듈 처리 (stub 방식):
- middleware.ts, supabase/utils/middleware.ts는 upstream과 100% 동일하게 유지
- Docker 빌드 시에만 @vercel/flags, @vercel/functions의 JS 구현을
  no-op stub으로 교체 (Dockerfile RUN cp 단계)
- .d.ts 타입 선언은 원본 유지 → TypeScript 빌드 보장
- Vercel 배포에는 영향 없음 (Dockerfile을 사용하지 않으므로)

기타:
- next.config.js output: 'standalone' 설정 (Docker 빌드용)
- BlockNoteEditor/utils.ts 에러 메시지 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@blackdew

blackdew commented Feb 1, 2026

Copy link
Copy Markdown
Contributor

🔍 PR #61 종합 리뷰 결과

리뷰 범위: +2495/-46 라인, 31개 파일 (신규 23개, 수정 8개)


🔴 Critical Issues (11건) - 머지 전 반드시 수정 필요

보안 관련

# 이슈 위치 설명
1 도메인 입력 검증 미흡 otu.sh 사용자 입력 도메인이 검증 없이 쉘 변수로 사용됨. regex 검증 추가 필요
2 환경 파일 권한 미설정 otu.sh docker/.env 파일 생성 후 chmod 600 필요

에러 핸들링

# 이슈 위치 설명
3 백업 Silent Failure otu.sh:cmd_backup pg_dump | gzip 파이프라인에서 pg_dump 실패 시에도 "백업 완료" 출력. set -o pipefail 및 검증 필요
4 Seed 데이터 오류 숨김 docker/entrypoint.sh:118-124 > /dev/null 2>&1로 모든 출력 숨김. 오류 내용 로깅 필요
5 마이그레이션 스키마 생성 검증 없음 docker/entrypoint.sh:38-46 ON_ERROR_STOP=1 없이 실행. 권한 문제 시 조용히 실패
6 마이그레이션 기록 INSERT 실패 미처리 docker/entrypoint.sh:83-87 기록 실패 시 다음 시작 때 재적용 → 데이터 중복 가능
7 lib-jwt.sh 로드 실패 미처리 otu.sh:52 파일 존재 확인 및 친절한 에러 메시지 필요
8 Docker Compose 실패 가이던스 없음 otu.sh 포트 충돌, 이미지 풀 실패 등 일반적 문제에 대한 도움말 추가 권장
9 init-roles.sh 누락 시 잘못된 성공 docker/db/init-roles.sh:9-12 POSTGRES_PASSWORD 미설정 시 exit 0 → exit 1로 변경 필요
10 Kong sed 실패 미검증 docker/kong/entrypoint.sh:27-31 출력 파일 생성 성공 여부 확인 필요
11 install-remote.sh 개별 다운로드 검증 없음 install-remote.sh 재시도 로직 및 개별 파일 검증 추가 권장

🟡 Important Issues (11건) - 권장 수정

# 이슈 위치 설명
1 Prettier 미적용 docker/stubs/@vercel/**/*.js CLAUDE.md 규칙 위반. npm run prettier 실행 필요
2 마이그레이션 체크 쿼리 에러 숨김 docker/entrypoint.sh:69-70 2>/dev/null || echo ""로 연결 실패 구분 불가
3 AI Provider 잘못된 값 Silent Fallback src/functions/ai/config.ts AI_PROVIDER=azure 같은 잘못된 값이 경고 없이 gateway로 폴백
4 환경변수 치환 검증 없음 docker/entrypoint.sh:139-146 치환 실제 발생 여부 미확인
5 generate-keys.sh openssl 오류 처리 없음 docker/generate-keys.sh openssl 실패 시 빈 키 생성 가능
6 git pull 오류 처리 없음 otu.sh:311-316 충돌 시 비일관 상태로 빌드 진행
7 AI 오류 시 컨텍스트 부족 src/functions/ai.js 입력 텍스트 길이/샘플 미포함
8 SQL 파라미터 바인딩 일관성 docker/entrypoint.sh 일부 쿼리만 -v 사용. 전체 통일 권장
9 sudo 사용 보안 경고 부족 install-remote.sh curl | sh 패턴 추가 경고 권장
10 'UNKNOWN' 반환 대신 상세 로깅 src/functions/ai/config.ts 디버깅에 도움되지 않음
11 runtime 주석 일관성 app/api/ai/*.tsx 일부만 상세 주석

💡 Suggestions (10건)

  1. 마이그레이션 전 DB 헬스체크 추가
  2. 백업 전 디스크 공간 확인
  3. 생성된 키 형식 검증
  4. DB 대기 루프 로그 빈도 조절 (매초 → 10초)
  5. 로그에 컨테이너 이름 prefix 추가
  6. JWT_SECRET 길이 주석 명확화 (32바이트 → ~44 base64 문자)
  7. DB_ENC_KEY 주석 명확화 (8바이트 → 16 hex 문자)
  8. 중복 SQL 파일 정리 (docker/db/ vs docker/volumes/db/)
  9. Kong 에러 메시지에 해결 방법 추가
  10. Supabase 서비스 버전 업데이트 안내 주석

✅ Positive Observations

  1. Multi-stage Docker 빌드: 3단계 빌드로 이미지 크기 최적화
  2. 보안 하드닝: DB 포트 localhost 바인딩, 비root 유저, healthcheck
  3. AI Provider 추상화: 셀프호스팅/Vercel 간 깔끔한 분리
  4. Idempotent 마이그레이션: 중복 실행 방지 + MIGRATION_FAIL_FAST
  5. 문서화 품질: docs/self-hosting.md 상세하고 구조적
  6. Stub 구현 주석: 목적과 사용 시점 명확

📋 권장 조치 순서

머지 전 반드시

  1. 백업 Silent Failure 수정 (데이터 안전성)
  2. 도메인 입력 검증 추가 (보안)
  3. 환경 파일 권한 설정 (보안)
  4. Seed 데이터 오류 로깅 (첫 실행 경험)
  5. 마이그레이션 오류 처리 (데이터 무결성)

머지 전 권장

  1. npm run prettier 실행
  2. AI Provider 잘못된 값 경고 추가
  3. init-roles.sh exit 코드 변경

🤖 Claude Code로 자동 생성된 리뷰입니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Docker 셀프호스팅에 Caddy 리버스 프록시 추가 (HTTPS 지원)

3 participants