배포 가이드
CONE-Watcher N 백엔드(heka-nhn-backend)와 프론트엔드(heka-nhn-frontend)의 이미지 빌드·배포 절차를 정리합니다. 두 리포는 동일한 GitOps 리포(cone-watcher-gitops) 의 동일한 kustomization 파일 을 공유하며, ArgoCD 가 이를 읽어 단일 cone-watcher 네임스페이스에 함께 배포합니다.
1. 배포 아키텍처 한눈에
모든 실제 배포 조작은 GitHub Actions workflow_dispatch 수동 트리거 입니다. 커밋 push 만으로는 아무것도 배포되지 않습니다.
2. 이미지 빌드 구조
2.1 백엔드 — base ↔ service 2단 계층
docker/base/Dockerfile 이 Python 런타임 + 의존성 + OS 도구를 묶어 별도 이미지(python:latest)로 먼저 만들어지고, 각 service(api / worker / scheduler) 이미지가 이를 FROM 으로 가져다 소스 파일만 얹습니다.
base 이미지 (${REGISTRY}/python:latest) — docker/base/Dockerfile
- multi-stage builder:
uv sync --frozen --no-dev로pyproject.toml+uv.lock에 선언된 모든 런타임 의존성을/usr/src/.venv에 설치 - 최종 이미지에
.venv복사 - OS:
wget,procps,wkhtmltox 0.12.6.1-3(청구서 PDF 렌더링용)
service 이미지 — docker/{api|worker|scheduler}/Dockerfile
즉 service 이미지는 base 위에 애플리케이션 소스 파일만 얹는 얇은 레이어입니다. 의존성 설치는 전혀 없습니다.
언제 base 를 다시 빌드해야 하는가
왜 service 이미지만 재빌드하면 안 되나? Service Dockerfile 의
FROM ${BASE_IMAGE_URI}는 빌드 시점의python:latest를 기준으로.venv를 그대로 가져옵니다. 새로 추가한 패키지가.venv에 없으므로 컨테이너 기동 시ModuleNotFoundError로 실패합니다.
2.2 프론트엔드 — 단일 Dockerfile 자체 완결
heka-nhn-frontend 는 pnpm workspace + turbo 모노레포이며, 각 앱(dashboard, admin)이 docker/{app}/Dockerfile 하나로 의존성 설치·빌드·최종 이미지까지 한 번에 처리합니다. 별도 base 이미지가 없습니다.
4-stage 빌드 구조 (두 Dockerfile 동일 패턴)
핵심 포인트:
- 백엔드와 달리 의존성 설치가 매번 빌드 안에서 일어남 —
pnpm-lock.yaml/package.json변경 시에도 별도 base 재빌드가 필요 없습니다. 그냥 해당 앱을 재빌드하면 됨. turbo prune덕분에 한 앱만 빌드할 때 다른 앱(예: dashboard 빌드할 때 admin 소스)은 이미지 레이어에 들어가지 않음.- 최종 이미지는 nginx 가 정적 파일을 서빙 하는 형태 (80 포트).
docker/{app}/nginx.conf가 번들됩니다. - 두 앱은 완전 독립.
dashboard만 변경됐으면admin은 재빌드할 필요 없음.
프론트엔드에서 재빌드가 필요한 경우
3. GitHub Actions 워크플로
3.1 Build Python — 백엔드 base 이미지 빌드 (heka-nhn-backend/.github/workflows/build-base.yml)
수동 트리거 전용 · 실행 빈도 드뭄.
- 입력:
environment(dev/prd) - 빌드 파일:
docker/base/Dockerfile - 태그:
${REGISTRY}/${PREFIX}/python:latest만 (커밋 SHA 태그 없음) :latest가 덮어써지므로, base 재빌드 직후 같은 커밋으로 service 를 빌드해야 의도한 base 를 사용합니다.
3.2 Build Service — 백엔드 서비스 이미지 빌드 + 배포 (heka-nhn-backend/.github/workflows/build-service.yml)
가장 자주 쓰는 백엔드 워크플로.
- 입력:
service(api/worker/scheduler),environment(dev/prd),deploy(기본true) - 3단계 job:
- check-image —
${PREFIX}/${service}:sha-${github.sha}이 이미 레지스트리에 있으면 빌드 스킵. - build —
BASE_IMAGE_URI=${REGISTRY}/${PREFIX}/python:latest로 service 이미지 빌드. 태그:latest+sha-<full-commit>. - deploy —
inputs.deploy == true이고 이미지가 준비됐으면cone-watcher-gitops의app/overlays/${environment}/kustomization.yaml에서cone/${service}이미지 태그를sha-${github.sha}로 갱신하는 PR을 생성·auto-merge. ArgoCD 가 auto-sync 로 파드 롤링 업데이트.
- check-image —
3.3 Build — 프론트엔드 이미지 빌드 + 배포 (heka-nhn-frontend/.github/workflows/build.yml)
프론트엔드는 단일 워크플로 가 전담합니다. 구조는 백엔드 Build Service 와 거의 동일.
- 입력:
service(dashboard/admin),environment(dev/prd),deploy(기본true) - 3단계 job:
- check-image — 동일 패턴 (
${PREFIX}/${service}:sha-${github.sha}). - build —
docker/${service}/Dockerfile을 빌드.BASE_IMAGE_URI같은 추가 인자 없음 — Dockerfile 자체가 완결돼 있기 때문. - deploy — 백엔드와 완전히 동일한 gitops 리포·동일한 kustomization 파일 을 수정:
cone/${service}이미지 태그 갱신 → PR auto-merge.
- check-image — 동일 패턴 (
주의 — 병행 배포 시 PR 충돌 가능성: 백엔드와 프론트엔드 양쪽의 deploy job 이 동시에
cone-watcher-gitops의 같은kustomization.yaml을 수정하려고 하면 한쪽 PR 이 머지될 때 다른 쪽이 rebase 가 필요해질 수 있습니다. 일반적으로update-kustomization액션이 처리하지만, 동시 실행을 피하고 순차적으로 트리거 하는 것을 권장합니다.
4. 배포 절차 (시나리오별)
공통 전제: 변경 사항이 해당 리포의 develop 브랜치에 merge 되어 있어야 하며, 빌드 대상은 merge 커밋 SHA 입니다.
시나리오 A — 백엔드 코드 변경만 (가장 흔함)
예: 컨트롤러 로직 수정, API 엔드포인트 추가
- PR 을
heka-nhn-backend/develop에 merge. - Actions → Build Service → Run workflow.
- 입력:
service=api(또는 변경된 서비스),environment=prd(혹은dev선행),deploy=true. - 자동 생성된 gitops PR 이 머지되는지 확인 → ArgoCD
Synced/Healthy대기 → 파드 rollout 확인. - 여러 서비스가 바뀌었으면 각각 반복 (api → worker → scheduler).
시나리오 B — 백엔드 Python 의존성 변경 (pyproject.toml / uv.lock)
예: 새 라이브러리 추가, 취약점 패치, dramatiq 등 핵심 패키지 업그레이드
base 재빌드 → service 재빌드 순서 엄수.
- 로컬에서
uv add <pkg>/uv lock --upgrade-package <pkg>등으로pyproject.toml+uv.lock갱신. 테스트 후 PR →developmerge. - Actions → Build Python → Run workflow (
environment선택). python:latest가 새.venv로 갱신 완료까지 대기 (몇 분).- 같은 커밋 기준으로 service 들 순차 빌드·배포:
- ArgoCD 동기화 및 파드 상태 확인.
⚠️ 주의 1 — base
:latest경주 조건: 다른 개발자가 동시에 다른 커밋으로 base 를 재빌드하면:latest가 덮어써집니다. 의존성 변경 배포 중에는 다른 사람에게 base 빌드 실행을 보류하도록 공유하세요.⚠️ 주의 2:
Build Python의environment입력은 GitHub Actions environment(시크릿/권한) 를 고르는 용도이지 이미지 태그를 나누지 않습니다. 결과물은 단일python:latest이라 dev/prd 가 같은 base 를 공유합니다.
시나리오 C — 백엔드 OS 패키지/도구 변경 (docker/base/Dockerfile)
절차는 시나리오 B 와 동일 — base 재빌드 후 service 전체 재빌드.
시나리오 D — 프론트엔드 코드 변경만 (가장 흔함)
예: 페이지 UI 수정, 컴포넌트 추가, 라우팅 변경
- PR 을
heka-nhn-frontend/develop에 merge. - Actions → Build → Run workflow.
- 입력:
service=dashboard(또는admin),environment=prd,deploy=true. - gitops PR 머지 확인 → ArgoCD sync → rollout.
packages/theme등 공용 패키지를 바꿨다면dashboard와admin둘 다 각각 빌드해야 합니다.
시나리오 E — 프론트엔드 의존성 변경 (pnpm-lock.yaml / package.json)
예:
@mui/material버전 업, 새 라이브러리 추가
백엔드와 달리 별도 단계 불필요 — 해당 앱을 재빌드하면 끝.
heka-nhn-frontend/develop에 lockfile 변경 커밋이 포함된 PR merge.- Actions → Build → Run workflow →
service선택 →--execute. - Dockerfile 의
depsstage 가 매번pnpm install --frozen-lockfile로 재설치하므로 자동 반영됩니다. - 공용 패키지(
packages/theme등)에 새 의존성이 추가됐다면 양쪽 앱을 각각 빌드.
시나리오 F — 환경변수·시크릿만 변경 (이미지 빌드 불필요)
예: SMTP 비밀번호 교체, 새 API 도메인, 인그레스 호스트 변경
주의: 스케줄러 cron(
config/envs/.env.scheduler)은 백엔드 이미지에 번들되므로 시나리오 A(코드 변경) 로 처리해야 합니다 — ConfigMap 이 아닙니다. 04번 가이드 §1.8 참조.
cone-watcher-gitops에서app/overlays/${environment}/kustomization.yaml의secretGenerator블록, 또는kustomize/mongodb/base/kustomization.yaml등 대상 경로 수정.- PR 생성 → 리뷰 → develop 머지.
- ArgoCD 가 자동으로 새 Secret 을 렌더링.
MONGODB_*/REDIS_*/SMTP_*등 파드 환경에 주입되는 값이면 파드 재기동 필요 (04번 가이드 §1.7):프론트엔드는 빌드 타임에.env*를 읽어 dist 에 박아 넣으므로, 프론트엔드 URL/키 변경은 시나리오 D 로 처리 하세요 (런타임 Secret 변경 불가).
5. 배포 확인
6. 롤백
방법 1 — GitOps PR revert (권장)
직전 배포로 만들어진 "chore: update CONE Watcher prd ... - <shortsha>" PR 을 cone-watcher-gitops 에서 revert. ArgoCD 가 이전 이미지 태그로 자동 재배포.
방법 2 — 이전 이미지 태그로 워크플로 재실행
이전에 성공했던 커밋 SHA 를 develop 에 다시 올리고 해당 리포의 Build Service (또는 프론트엔드 Build) 를 실행. check-image job 이 이미 존재하는 이미지를 감지해 빌드는 스킵하고 deploy job 만 수행하므로 빠릅니다.
방법 3 — 긴급 수동 (마지막 수단)
ArgoCD auto-sync 가 다시 덮어쓰기 전까지의 짧은 응급조치로만:
→ ArgoCD 가 다시 동기화하면 GitOps 기준으로 돌아가므로 반드시 방법 1 또는 2 로 영구화 해야 합니다.
7. 체크리스트
배포 전
- 변경이 해당 리포의
develop에 merge 됐는가? 머지 커밋 SHA 를 확인했는가? - 백엔드:
pyproject.toml/uv.lock/docker/base/Dockerfile중 하나라도 변경됐다면Build Python을 먼저 실행했는가? - 프론트엔드:
packages/theme등 공용 패키지를 바꿨다면 영향받는 앱 모두 배포 대상에 포함했는가? - 백엔드·프론트엔드 동시 배포 상황인가? 같은 gitops 파일(
kustomization.yaml) 을 수정하므로 순차 실행을 권장. - dev 에서 한번 돌려본 뒤 prd 로 가는 절차를 따랐는가? (긴급 패치가 아니라면)
배포 후
- ArgoCD Application
Synced/Healthy? - 파드 이미지 태그가
sha-<머지 커밋>과 일치? - 모든 rollout 완료 (
rollout statussuccess)? -
error_log및 워커 로그에 배포 직후 새 에러가 쌓이지 않는가? - 청구서 배치 등 배치 작업이 예정된 날이라면 실행 시각 전후로 이상 없는지 추가 확인?
- 프론트엔드 배포면 실제 브라우저에서 주요 화면 로드·로그인·주요 액션 smoke test 완료?
8. FAQ / 주의점
Q. 백엔드 base 이미지는 왜 매 커밋마다 빌드하지 않나? A. 의존성이 거의 바뀌지 않고, wkhtmltopdf 설치가 느립니다(수 분). 매번 빌드하면 CI 시간·레지스트리 용량 낭비. 의존성이 바뀔 때만 재빌드하는 것이 합리적.
Q. 프론트엔드도 base 이미지를 두면 더 빨라지지 않나? A. turbo prune + pnpm store cache mount 덕분에 변경 없는 의존성은 Docker 레이어/캐시에서 재사용됩니다. 현재 구조로도 빌드 시간이 충분히 짧고, base 이미지를 분리하면 node_modules 의 workspace 해석과 turbo prune 이 복잡해집니다. 지금 방식이 단순성·속도 균형이 좋습니다.
Q. check-image job 이 왜 필요한가?
A. 같은 커밋을 dev 에 배포한 뒤 prd 에 배포할 때 빌드를 다시 할 필요가 없습니다. 레지스트리에 이미지가 이미 있으면 바로 deploy job 만 돌리는 최적화.
Q. 백엔드 .env 가 이미지에 copy 되는데 시크릿이 이미지에 박히는 것 아닌가?
A. docker/{service}/Dockerfile 에 COPY .env .env 가 있지만, 이 .env 는 로컬 개발용 빈 껍데기 입니다. 운영 환경에서는 Kubernetes Secret prd-cone-watcher-env 의 값이 envFrom 으로 컨테이너 env 에 덮어씌워집니다 (app/base/worker/deployment.yaml 등). 이미지 안의 .env 는 사실상 무시됩니다. (향후 .dockerignore 로 .env 를 제외하는 것을 고려.)
Q. 프론트엔드 환경변수는 어디서 주입되나?
A. Vite 는 빌드 타임에 .env* 를 읽어 dist 에 상수로 인라인합니다. 즉 프론트엔드 환경변수 변경은 런타임 Secret 으로 못 바꾸고, 리포의 .env* 수정 → 재빌드 가 정공법입니다. 시나리오 D 로 처리하세요. 현재는 동일한 번들이 dev/prd 에 모두 나가므로, 환경별 분기가 필요하면 VITE_* 환경변수를 Dockerfile build-args 로 주입하는 구조를 별도로 설계해야 합니다.
Q. 스케줄러 cron 변경은 왜 이미지 재빌드가 필요한가?
A. config/envs/.env.scheduler 가 소스 트리에 있고 docker/scheduler/Dockerfile 의 COPY config config 로 이미지에 들어가기 때문입니다. ConfigMap 으로 외부화돼 있지 않아 런타임 값 변경 불가능. 04번 가이드 §1.8 참조.
Q. dev 와 prd 의 base 이미지가 같은데 괜찮은가?
A. 현재 백엔드 python:latest 는 단일 태그로 공유합니다. 의존성 변경을 dev 에서 먼저 검증한 뒤 prd 에 동일한 base 로 배포하므로 일관성은 유지됩니다. 다만 dev 에 새 base 를 올린 직후 검증 없이 prd service 를 빌드하면 새 base 로 덮어씌워진 상태로 prd 에 나가므로, dev 배포 완료 후 일정 관찰 기간을 두고 prd service 빌드 로 진행하는 것이 안전합니다. 프론트엔드는 base 가 없어 이 이슈가 없습니다.
Q. 백엔드·프론트엔드 동시 배포 중 gitops PR 이 충돌하면?
A. grumatic/actions/update-kustomization@v1 은 최신 base 로 rebase 후 재시도하도록 구현돼 있어 대부분 자동 해결됩니다. 그래도 운영상 같은 환경(prd) 에 backend + frontend 를 동시 트리거하지 말고 순차적으로 실행하는 것을 권장. 병행이 불가피하면 각 워크플로 실행 로그에서 PR 머지 완료를 확인하고 다음 트리거로 넘어가세요.