배포 & 릴리스
너홀로프로는 main 브랜치 = production 입니다. merge 후 수동 rollout 으로 배포합니다 — App Hosting rolloutPolicy.disabled=true 라 main push 만으로는 자동 배포되지 않으며, 배포자가 Firebase Console / CLI 로 rollout 을 트리거해야 한다 (2026-06-07 정정: 과거 "자동 배포" 표기는 실제 backend 설정과 불일치였다). 별도의 자동 태깅·GitHub Release 는 사용하지 않습니다 (2026-05-01 폐기) — 배포 단위는 main 의 commit SHA, 롤백은 App Hosting 의 SHA 단위 rollback.
feature 브랜치에서 PR 을 거쳐야만 main 에 반영됩니다. 긴급 hotfix 도 PR + 리뷰 경로를 따릅니다.
1. 배포 토폴로지
3개 앱이 각각 다른 Firebase 서비스로 배포됩니다.
| 앱 | 방식 | 백엔드 ID / 호스팅 사이트 | URL |
|---|---|---|---|
apps/web | Firebase App Hosting (Next.js, Cloud Run) | neohollo-pro (asia-east1) | https://pro.neohollo.com |
apps/ops | Firebase App Hosting (Next.js, Cloud Run) | neohollo-pro-admin (asia-east1) | https://admin-pro.neohollo.com |
apps/docs | Firebase Hosting (정적 빌드) | docs-pro-neohollo | https://docs-pro.neohollo.com |
functions/ | Cloud Functions v2 | codebase default | — |
Firestore + Storage 는 단일 공유. Firestore 위치 asia-northeast3. (2026-06-11 정정 — ops 백엔드 ID 는 neohollo-admin 이 아니라 neohollo-pro-admin, App Hosting 두 백엔드 모두 asia-east1. App Hosting Admin API 실측.)
2. 릴리스·버전 정책
별도 자동 태깅·릴리스 없음. package.json 의 version 필드는 더 이상 자동 갱신되지 않으며, 배포 단위·롤백 기준은 main 의 commit SHA 입니다.
- 배포 단위: main 의 각 commit SHA (Firebase App Hosting rollout 1:1 매핑)
- 변경 이력:
git log+ GitHub PR 목록 - 롤백: §4 "롤백 (App Hosting)" — Cloud Run revision 단위, main revert 없이도 가능
- 수동 GitHub Release 가 필요한 경우 (예: 외부 공지용 milestone):
gh release create vX.Y.Z로 직접 발행
3. 품질 게이트
GitHub Actions 자동 trigger 는 사용하지 않습니다 (2026-05-18 — 전 워크플로 workflow_dispatch 단독. 솔로 dev 환경에서 Actions billing 차단 시 가짜 FAILURE 로 보호막이 0 이 되는 사고 방지). 품질 게이트 SSoT 는 로컬 pre-push hook:
pnpm test && pnpm lint # pre-push hook 이 자동 실행
pnpm --filter=@neohollo/web build # next build 회귀 (ADR 0030 T1) — 큰 변경 시 수동
E2E (Playwright) 는 게이트에 포함되지 않음 — 로컬 에뮬레이터 의존. UI 대규모 변경 시 로컬 pnpm e2e 통과 후 PR. nightly 회귀는 local launchd, production smoke 는 배포 직후 manual gh workflow run.
4. App Hosting 배포 (apps/web · apps/ops)
Firebase App Hosting 은 자동 rollout 이 비활성(rolloutPolicy.disabled=true)이라, main merge 후 수동으로 rollout 을 트리거해야 배포됩니다 (Firebase Console > App Hosting > backend 에서 새 rollout 생성, 또는 CLI). 별도 GitHub Actions deploy 단계 없음 (App Hosting backend 자체가 GitHub 통합).
apps/web/apphosting.yaml—buildCommand: pnpm run build,minInstances: 0, Firebase Admin 시크릿 3종 + Resend 주입apps/ops/apphosting.yaml—buildCommand: pnpm --filter=@neohollo/ops... build, 서버 시크릿 없음 (client SDK 만)
배포 진행·현황 확인
가장 빠른 경로는 ops 콘솔 /health 의 "App Hosting 배포 현황" 패널 (#2751) — 백엔드별 서빙 build 의 커밋 sha (GitHub 링크) · 커밋/빌드 시각 (KST) 과 "최신 build 미서빙" 드리프트 (P1) 를 한 화면에 표시한다.
CLI:
firebase apphosting:rollouts:list --backend=neohollo-pro
firebase apphosting:rollouts:list --backend=neohollo-pro-admin
Firebase Console → App Hosting → 백엔드 선택 → 최근 rollout 상태(BUILDING · DEPLOYING · SUCCEEDED · FAILED) 확인.
롤백 (App Hosting)
- Firebase Console → App Hosting → 해당 backend
- Rollouts 탭에서 이전 성공 rollout 선택
- "Rollback" — Cloud Run 트래픽을 이전 리비전으로 100% 이동
- 완료 후 root cause 분석 → 수정 PR → 새 rollout 으로 전진 복구
App Hosting 롤백은 Cloud Run 레벨에서 트래픽만 전환하므로 main 브랜치의 상태와 분리됩니다. 즉 git revert 없이도 즉시 복구 가능. 급한 장애는 먼저 롤백 → 그 다음 원인 분석·정식 revert PR 순.
5. Cloud Functions 배포 (수동)
Cloud Functions 는 자동 배포 대상이 아닙니다. App Hosting 배포와 별도로 수동 실행.
# functions/ 의존성 설치 (별도 npm workspace)
cd functions
npm install
# 빌드·린트 (firebase.json predeploy 가 재실행)
npm run lint
npm run build
# 배포
cd ..
firebase deploy --only functions
혹은 특정 function 만:
firebase deploy --only functions:onCaseClosedLegacyPipeline
현재 배포된 Functions
| 파일 | 역할 |
|---|---|
cleanup.ts | 주기적 cleanup (Scheduled) |
email.ts | Resend 발송 헬퍼 |
hearings-stats.ts | 기일 통계 집계 |
legacy-pipeline.ts | 사건 종결 시 OCR + DLP + Vertex AI Search 인덱싱 |
legacy-retry.ts | 파이프라인 재시도 |
plan-downgrade.ts | 트라이얼 만료·grace 경과 시 자동 다운그레이드 (Scheduled) |
plan-trial-reminders.ts | 트라이얼 만료 리마인더 (Scheduled) |
reminders.ts | 매일 09:00 KST 기일 알림 (Scheduled) |
stats.ts | aggregateCaseStats (사건 통계 갱신) |
Cloud Functions 긴급 비활성화
특정 function 이 사고를 일으키면:
# 함수 자체 삭제
firebase functions:delete <functionName>
# 또는 소스에서 export 제거 → 재배포 (선택적)
# functions/src/index.ts 에서 해당 export 를 주석 처리
Scheduled function 은 Google Cloud Console → Cloud Scheduler 에서 일시 정지 도 가능 (삭제 대신).
6. Firestore Rules · 인덱스 배포
firebase deploy --only firestore:rules
firebase deploy --only firestore:indexes
firebase deploy --only storage # storage.rules
Rules 배포는 즉시 반영 — 잘못된 rule 이 올라가면 바로 접근 차단. 반드시 로컬 에뮬레이터에서 검증 후 배포.
firebase emulators:start --only firestore
# → http://localhost:4000 (에뮬레이터 UI) 에서 Rules Playground 로 쿼리 검증
7. docs 사이트 배포 (apps/docs)
pnpm build --filter=@neohollo/docs # apps/docs/build/ 정적 출력
firebase deploy --only hosting:docs-pro-neohollo
Firebase Hosting 은 main push 시 자동 배포되지 않음 (App Hosting 과 다름). 문서 수정 PR 이 main 에 merge 된 후 수동 배포 가 현재 절차.
docs 는 정적이라 GitHub Actions 에서 firebase deploy --only hosting:docs-pro-neohollo 를 main push trigger 로 추가하는 것이 자연스러운 개선. 아직 미구현.
8. 환경 분리 전략 (현재 상태)
현재는 단일 Firebase 프로젝트에서 프로덕션을 운영합니다. 스테이징 분리는 미구현.
로컬 개발은 Firebase 에뮬레이터로 대체:
| 에뮬레이터 | 포트 |
|---|---|
| Auth | 9099 |
| Firestore | 8080 |
| Functions | 5001 |
| Storage | 9199 |
| Hosting (docs) | 5000 |
| App Hosting (ops) | 5003 |
| Emulator UI | 4000 (기본) |
firebase emulators:start
Playwright E2E 는 localhost:5002 를 가리킵니다 (테스트 가이드 §3).
9. 긴급 대응 절차
| 증상 | 1차 대응 | 2차 대응 |
|---|---|---|
| 프로덕션 web 500 에러 | App Hosting 이전 rollout 으로 롤백 | git revert + PR 로 정식 복구 |
| Cloud Function 오동작 | firebase functions:delete 또는 Scheduler 일시정지 | 수정 후 firebase deploy --only functions:<name> |
| Firestore Rules 실수 배포 | 이전 rules 파일로 firebase deploy --only firestore:rules | rules E2E 테스트 추가 |
| 데이터 오염 | 즉시 해당 Function 비활성화 | Firestore backup 에서 복구 (GCP Console) |
| 비정상 Gemini 요금 | config/appMetadata.features.ai.* 플래그 OFF (Firestore 직접 편집) | kill switch PR 병합 |
config/appMetadata.features.infraHardening.* 플래그는 Firestore 문서 1개 편집으로 원격 롤백이 가능하도록 설계되어 있습니다. 배포 없이 즉시 적용. 새 보안 기능은 이 네임스페이스에 플래그로 감싸세요 (CLAUDE.md 참조).
10. 배포 전 체크리스트
- Conventional Commits 프리픽스 적절
- 로컬 게이트 통과 (pre-push hook: test · lint / 큰 변경은 web build 까지)
- AI / 플랜 / Firestore 스키마 수정 시 metadata 4곳 + Firestore 동기화 (
CLAUDE.md§"앱 메타데이터" 참조) - 신규 Cloud Function 은 수동
firebase deploy --only functions별도 수행 - CSP 도메인 추가 시
csp-headers.ts+ 해당 테스트 업데이트 - Firestore Rules 수정 시 에뮬레이터 검증
- docs 수정 포함 시 로컬
pnpm build --filter=@neohollo/docsbroken link 0건
11. 배포 후 체크리스트 (2026-06-11 사고 후속)
merge ≠ deploy 환경의 함정 두 가지를 rollout 직후 반드시 확인한다 (회고 참조):
- 서빙 커밋 확인 — ops
/health"App Hosting 배포 현황" 조회: 서빙 build 의 커밋 sha 가 의도한 커밋인지. rollout 직후 머지된 커밋은 포함되지 않는다 (2026-06-11 같은 날 2회 재발한 패턴 — rollout 트리거 시점의 main 이 빌드 대상). 직후 머지가 있으면 재롤아웃 여부 판단 - 열린 탭 skew 인지 — 배포 전에 열려 있던 모든 탭의 Server Action 은 404 (
failed-to-find-server-action). 사용자에게는 #2749 가 한국어 "새 버전이 배포되어…" 안내 + 새로고침 버튼을 띄우지만, 본인 작업 탭도 새로고침 필요. 근본 완화는NEXT_SERVER_ACTIONS_ENCRYPTION_KEY고정 (후속 작업 — 키 고정 시 변경 없는 액션의 ID 가 빌드 간 유지) - 직전 작업이 특정 기능 fix 였다면 production 에서 해당 경로 1회 실측 (예: 초대 fix → 재발송 1회로 stuck 초대 복구)
- 큰 기능 배포 후 5-10분 내 Sentry · Firebase Console 오류 모니터링