Grace
grace's dev_note
Grace
전체 방문자
오늘
어제
  • 분류 전체보기
    • FrontEnd
      • Next.js
      • React
      • ReactNativ..
      • Vue
    • Javascript
      • 러닝 자바스크립트
      • 모던 자바스크립트
    • CS
    • DataScienc..
      • Data Struc..
      • LeetCode
    • BackEnd
      • Express
      • Node.js
      • Nest.js
    • DevOps
      • Docker
    • 매일메일
    • 회고
    • 코드캠프

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • React Native
  • Vue
  • PostgreSQL
  • Vue3
  • 자바스크립트
  • 번들러
  • pinia
  • vitejs
  • node.js
  • postgres
  • backend
  • Vite
  • javascript
  • vue-query
  • Express
  • tanstack
  • nest.js
  • Vue.js
  • 알고리즘
  • 함수

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Grace

grace's dev_note

웹 모노레포 CI/CD 개편기 — 라벨 배포에서 태그 배포로
FrontEnd/React

웹 모노레포 CI/CD 개편기 — 라벨 배포에서 태그 배포로

2026. 4. 17. 12:48

Nx 웹 모노레포에서 PR 라벨 기반 배포를 걷어내고, Git 태그 + Doppler로 배포 파이프라인을 재설계한 과정을 공유합니다.


들어가며

저희 팀은 Nx 기반 웹 모노레포에서 여러 앱을 운영하고 있습니다. 관리자 페이지, 백오피스, 로그인 페이지, 웹뷰 등 성격이 다른 앱들이 하나의 레포에 들어 있고, 각각 독립적으로 빌드/배포됩니다.

초기에는 PR 라벨로 배포를 트리거하고, 환경변수는 GitHub Secrets에 앱별로 등록하는 방식이었습니다. 앱이 2~3개일 때는 문제가 없었지만, 앱과 환경이 늘어나면서 관리가 어려워졌습니다.

이 글에서는 기존 방식의 문제점, 태그 기반 배포로의 전환, Doppler를 활용한 환경변수 관리, 그리고 최종 CI/CD 파이프라인 구조를 다룹니다.


기존 방식의 문제점

PR 라벨 기반 배포

기존에는 PR에 라벨을 붙여서 어떤 앱을 어떤 환경에 배포할지 결정했습니다.

PR에 "deploy:care-admin:dev" 라벨 → 머지 시 care-admin dev 배포
PR에 "deploy:care-admin:prod" 라벨 → 머지 시 care-admin prod 배포

앱이 적을 때는 괜찮았지만, 앱 × 환경 조합이 늘어나면서 문제가 생겼습니다.

문제 상황

라벨 조합 폭발 앱 7개 × 환경 2~4개 = 라벨 20개 이상. 어떤 라벨을 붙여야 하는지 헷갈림
실수 위험 라벨을 잘못 붙이면 의도하지 않은 앱/환경에 배포. 라벨을 빼먹으면 배포 누락
배포 시점 제어 불가 PR 머지 = 배포. "코드는 머지하되 배포는 나중에"가 불가능
이력 추적 어려움 "이 앱이 마지막으로 언제 배포됐지?" → PR 라벨을 하나하나 뒤져야 함

GitHub Secrets 직접 관리

앱별 환경변수(S3 버킷, CloudFront Distribution ID 등)를 GitHub Secrets에 개별 등록했습니다.

AWS_S3_BUCKET_CARE_ADMIN_DEV
AWS_S3_BUCKET_CARE_ADMIN_PROD
AWS_CLOUDFRONT_ID_CARE_ADMIN_DEV
AWS_CLOUDFRONT_ID_CARE_ADMIN_PROD
AWS_S3_BUCKET_BACKOFFICE_DEV
AWS_S3_BUCKET_BACKOFFICE_PROD
...

문제 상황

Secrets 수 폭발 앱 추가 시마다 S3 버킷, CloudFront ID, 기타 설정을 수동 등록
이중 관리 로컬 개발용 .env와 CI Secrets를 따로 관리. 값이 달라져도 모름
가시성 부족 GitHub Secrets는 값을 볼 수 없음. "이 값이 맞나?" 확인 불가

개편 방향

두 가지를 바꿨습니다.

  1. 배포 트리거: PR 라벨 → Git 태그
  2. 환경변수 관리: GitHub Secrets 직접 관리 → Doppler

태그 기반 배포

태그 네이밍 컨벤션

{앱이름}/{환경}/YYYY.MM.DD[.N]
  • 날짜 기반 버전을 사용합니다. SemVer가 필요한 라이브러리가 아니라 배포 단위의 웹 앱이기 때문입니다.
  • 같은 날 여러 번 배포 시 suffix를 붙입니다: .1, .2, ...

태그 설명

care-admin/dev/2026.04.17 care-admin을 dev 환경에 배포
care-admin/prod/2026.04.17 care-admin을 prod 환경에 배포
care-admin/prod/2026.04.17.1 같은 날 prod 재배포
rehab-manager/dev/2026.04.17 rehab-manager를 dev 환경에 배포

배포 방법

# CLI
git tag care-admin/dev/2026.04.17
git push origin care-admin/dev/2026.04.17

GitHub GUI로도 가능합니다. Releases → Draft a new release → 태그 이름 입력 → Create new tag → Publish release.

라벨 대비 장점

PR 라벨 Git 태그

배포 시점 PR 머지 시 (제어 불가) 태그 push 시 (원하는 시점)
이력 추적 PR 라벨 뒤지기 git tag -l "care-admin/prod/*"
실수 가능성 라벨 선택 실수 태그 이름이 곧 배포 대상이라 명확
롤백 이전 PR 찾아서 재배포 이전 태그의 커밋에서 새 태그 생성

Doppler로 환경변수 관리

Doppler는 환경변수를 중앙에서 관리하는 서비스입니다. 앱별, 환경별로 config를 만들어두면 로컬 개발과 CI 양쪽에서 동일한 환경변수를 사용할 수 있습니다.

기존 vs 개편

Before: 앱별 S3 버킷, CloudFront ID 등을 GitHub Secrets에 개별 등록

# 워크플로우에서 Secrets를 하나하나 꺼내서 주입
env:
  S3_BUCKET: ${{ secrets.AWS_S3_BUCKET_CARE_ADMIN_DEV }}
  CLOUDFRONT_ID: ${{ secrets.AWS_CLOUDFRONT_ID_CARE_ADMIN_DEV }}
  VITE_API_URL: ${{ secrets.VITE_API_URL_CARE_ADMIN_DEV }}

After: Doppler config 이름 하나만 넘기면 환경변수가 자동 주입

# 워크플로우에서 doppler run으로 실행하면 끝
- run: doppler run --project ipixel-admin --config dev_care_manager -- bash scripts/deploy.sh

GitHub Secrets에 남는 것

Doppler 도입 후 GitHub Secrets에는 인프라 인증 정보만 남깁니다.

Secret 용도

DOPPLER_TOKEN_* 앱/환경별 Doppler 접근 토큰
AWS_ACCESS_KEY_ID AWS 인증
AWS_SECRET_ACCESS_KEY AWS 인증
AWS_REGION AWS 리전

앱별 S3 버킷, CloudFront ID, API URL 등은 전부 Doppler에서 관리합니다. 앱이 추가되어도 GitHub Secrets에 등록할 건 Doppler 토큰 하나뿐입니다.

로컬 개발에서의 사용

# 앱별 .env 파일 다운로드
nx run care-admin:doppler   # → .env 파일 생성

CI에서는 doppler run으로 환경변수를 직접 주입하므로 .env 파일이 필요 없습니다.


CI/CD 파이프라인 구조

워크플로우 파일

파일 역할

workspace-ci.yml PR 시 nx affected --target=build로 영향받은 앱만 빌드 검증
workspace-cd.yml 태그 push 트리거. 태그 prefix로 앱/환경을 분기하여 cd.yml 호출
cd.yml 재사용 워크플로우. 실제 빌드 + 배포 실행

핵심은 workspace-cd.yml과 cd.yml의 분리입니다. workspace-cd.yml은 태그를 파싱해서 "어떤 앱을, 어떤 환경으로" 배포할지 결정하고, 실제 빌드/배포 로직은 cd.yml에 위임합니다.

cd.yml — 재사용 워크플로우

# cd.yml (재사용 워크플로우)
inputs:
  script-path     # 실행할 배포 스크립트 경로
  doppler-config  # Doppler config 이름

secrets:
  DOPPLER_TOKEN
  AWS_ACCESS_KEY_ID
  AWS_SECRET_ACCESS_KEY
  AWS_REGION

모든 앱의 배포가 동일한 흐름을 따릅니다:

코드 체크아웃
→ Doppler CLI 설치
→ node_modules 캐시 복원
→ AWS 인증
→ doppler run으로 환경변수 주입 + 배포 스크립트 실행

배포 스크립트 프로세스

각 앱의 배포 스크립트(scripts/deploy_*.sh)는 동일한 구조입니다:

#!/bin/bash
set -e

# 1. 의존성 설치
yarn install --immutable

# 2. 빌드 (Nx 캐시 스킵 — CI에서는 항상 fresh 빌드)
yarn build:care-dev --skip-nx-cache

# 3. 기존 파일 삭제 + 빌드 결과물 업로드
aws s3 rm s3://$S3_BUCKET --recursive
aws s3 sync dist/ s3://$S3_BUCKET --delete

# 4. index.html에 no-cache 메타데이터 설정
aws s3 cp s3://$S3_BUCKET/index.html s3://$S3_BUCKET/index.html \
  --metadata-directive REPLACE --cache-control "no-cache"

# 5. CloudFront 캐시 무효화
aws cloudfront create-invalidation \
  --distribution-id $CLOUDFRONT_ID --paths "/*"

S3_BUCKET, CLOUDFRONT_ID 등의 환경변수는 스크립트에 하드코딩하지 않습니다. doppler run이 Doppler config에서 가져와 자동으로 주입해줍니다. 덕분에 하나의 스크립트 구조로 모든 앱/환경을 처리할 수 있습니다.

set -e가 적용되어 있어서 빌드가 실패하면 S3 업로드 없이 즉시 중단됩니다.

CI 흐름 (PR)

PR이 main으로 열리면:

PR → nx affected --target=build → 변경된 앱만 빌드 검증

전체 앱을 빌드하지 않고 nx affected로 변경된 앱만 검증합니다.

CD 흐름 (배포)

태그가 push되면:

태그 push
→ workspace-cd.yml: 태그 prefix로 앱/환경 분기
→ cd.yml: Doppler config + 배포 스크립트 전달
→ doppler run으로 환경변수 주입 + 스크립트 실행
→ S3 업로드 + CloudFront 캐시 무효화

전체 구성 요약

항목 Before After

배포 트리거 PR 라벨 Git 태그 (앱/환경/날짜)
환경변수 관리 GitHub Secrets (앱별 개별 등록) Doppler (중앙 관리)
CI 검증 전체 빌드 nx affected (변경된 앱만)
배포 워크플로우 앱별 개별 워크플로우 workspace-cd.yml + cd.yml 재사용 구조
롤백 이전 PR 찾아서 재배포 이전 태그 커밋에서 새 태그 생성

인프라 구성

항목 기술

호스팅 AWS S3 (정적 파일)
CDN AWS CloudFront
환경변수 Doppler
빌드 도구 Nx + Vite
패키지 매니저 Yarn Berry (PnP)
CI/CD GitHub Actions

주의사항

  • macOS 대소문자: macOS는 파일명 대소문자를 구분하지 않지만, CI(Linux)에서는 구분합니다. import 경로의 대소문자를 정확히 맞춰야 CI에서 빌드가 깨지지 않습니다.
  • 빌드 실패 시 안전장치: 모든 배포 스크립트에 set -e가 적용되어 있어, 빌드가 실패하면 S3 업로드 없이 즉시 중단됩니다.
  • 롤백 방법: 이전 태그의 커밋에서 새 태그를 생성하여 재배포하면 됩니다. 별도의 롤백 메커니즘은 필요 없습니다.

마치며

정리하면 이번 개편에서 바꾼 건 두 가지입니다.

  1. 배포 트리거를 PR 라벨에서 Git 태그로 — 배포 시점을 명시적으로 제어하고, 태그 자체가 배포 이력이 됩니다.
  2. 환경변수를 GitHub Secrets에서 Doppler로 — 로컬/CI 환경변수를 한 곳에서 관리하고, 앱 추가 시 Secrets 수동 등록 작업을 없앴습니다.

특별히 복잡한 기술을 도입한 것은 아닙니다. 태그 네이밍 컨벤션을 정하고, 재사용 워크플로우로 중복을 제거하고, 환경변수 관리를 외부 서비스로 위임한 것뿐입니다. 하지만 이 세 가지만으로도 "이 앱 어디에 배포된 거지?", "이 환경변수 값이 맞나?" 같은 질문이 사라졌습니다.

모노레포에서 앱이 늘어날수록 CI/CD 관리 비용은 빠르게 증가합니다. 비슷한 고민을 하고 계시다면, 태그 컨벤션 하나 정하는 것부터 시작해보시길 추천드립니다.

저작자표시 비영리 변경금지 (새창열림)

'FrontEnd > React' 카테고리의 다른 글

Yarn Berry PnP 마이그레이션 — node_modules를 없앤 이유와 과정  (0) 2026.04.17
React에서 CRA보다 Vite가 좋다고? - 프로덕션 버전으로 빌드하기  (0) 2022.12.27
MSW로 프론트엔드 개발 생산성 높이기! - MSW 사용하기  (0) 2022.12.26
MSW로 프론트엔드 개발 생산성 높이기! - MSW 알아보기  (0) 2022.12.26
MSW로 프론트엔드 개발 생산성 높이기! - Mocking을 해야하는 이유  (0) 2022.12.23
    'FrontEnd/React' 카테고리의 다른 글
    • Yarn Berry PnP 마이그레이션 — node_modules를 없앤 이유와 과정
    • React에서 CRA보다 Vite가 좋다고? - 프로덕션 버전으로 빌드하기
    • MSW로 프론트엔드 개발 생산성 높이기! - MSW 사용하기
    • MSW로 프론트엔드 개발 생산성 높이기! - MSW 알아보기
    Grace
    Grace
    기술 및 회고 블로그

    티스토리툴바