본문 바로가기
[Project] 프로젝트

AWS S3 + CloudFront + GitHub Actions로 운영/개발 환경 CI/CD 구축

by 지공A 2025. 8. 14.

GitHub Actions를 이용해 브랜치별로 다른 환경(dev/prod)을 자동 배포하는 방법을 정리해봤다.

시작 계기

우리 팀은 프로젝트 런칭을 준비하면서 운영 서버(API)운영 도메인을 새로 만들었다.
런칭 전에는 운영 도메인이 없었고, 프론트엔드 개발 환경에서 호출하는 API도 모두 하나뿐이었다.
기존 개발 도메인 역시 운영 서버 API를 그대로 호출하고 있었는데,

런칭 이후 안정적인 운영을 위해 바로 운영 환경에 코드를 배포하기보다, 개발 서버 배포 → QA → 운영 배포의 순서를 지키기로 했다.
따라서 개발 환경에서는 운영 API가 아니라, 개발 서버 API를 바라보도록 도메인을 다시 구성할 필요가 생겼다.

 

목표

  • 개발 도메인 → 개발 서버 API 로 호출하도록 수정
  • 운영 도메인 → 운영 서버 API 로 호출하도록 유지
  • GitHub Actions를 이용해
    • main 브랜치 → 운영 도메인 배포 (기존)
    • dev 브랜치 → 개발 도메인 배포
      자동 배포 파이프라인(CI/CD) 구성

1. 사전 준비

  1. AWS S3 버킷 생성
    • 버킷 명은 예
      • prod-bucket (운영) → 유지
      • dev-bucket (개발) → 생성
    • AWS 콘솔 → S3 → 버킷 만들기(Create bucket) 클릭
    • AWS 리전: 운영과 같은 리전으로 선택 (ex. ap-northeast-2, 서울)
    • 퍼블릭 액세스 차단 설정: 모든 퍼블릭 액세스 차단체크 유지 (CloudFront에서 접근하도록 설정할 거라 공개 안 함)
    • 나머지 기본값 그대로 두고 버킷 만들기(Create bucket) 클릭
    • 로컬에서 npm run build → dist/ 생성
    • dist 안의 내용물(index.html, assets/ 등)을 S3 버킷 루트에 업로드

 

  2. AWS CloudFront 배포 생성

  • 운영용 배포 → 기존 연결 유지
  • 개발용 배포 → dev-bucket 연결
  • 배포 ID를 메모 (GitHub Secrets에서 사용)
  • 싱글페이지 앱이라서 /admin/login 같은 경로 새로고침 시 404가 날 수 있기 때문에 CloudFront에서 SPA 리다이렉트 설정 필요
  • SPA 라우팅 404/403 처리
    1. CloudFront 배포 → Error pages
    2. Create custom error response
      • HTTP error code: 403
      • Customize error response: Yes
      • Response page path: /index.html
      • HTTP Response code: 200
      • TTL: 0
    3. 같은 방법으로 404도 /index.html → 200으로 설정
  • Alternate domain name(CNAME)에 개발 도메인 추가
  • SSL certificate에서 인증서 선택 → 우리는 이미 개발 도메인까지 커버하는 인증서기 때문에, 기존 인증서를 선택했다.
  • Default root object → index.html 입력
  • 배포 생성 후 Route53에서 CNAME 연결

 

  3. AWS IAM 사용자 생성 (배포 전용)

  • S3, CloudFront 권한 부여
  • Access KeySecret Access Key 발급 → GitHub Secrets에 저장
    • Secret Access Key는 꼭 메모해두자!

  4. GitHub 저장소 준비

  • main, dev 브랜치 존재

2. GitHub Secrets & Variables 설정

2.1 Environments 생성

  1. GitHub 레포 → Settings → Environments
  2. prod와 dev 환경 생성

2.2 각 환경에 Variables/Secrets 등록

  필수 Variables (환경 변수)

  • VITE_API_BASE_URL — API 서버 URL
  • AWS_REGION — AWS 리전 (ap-northeast-2 등)
  • AWS_S3_BUCKET — 해당 환경의 S3 버킷 이름

  필수 Secrets (민감정보)

  • AWS_ACCESS_KEY_ID — IAM 액세스 키 ID
  • AWS_SECRET_ACCESS_KEY — IAM 시크릿 액세스 키
  • AWS_CLOUDFRONT_DISTRIBUTION_ID — 해당 환경 CloudFront 배포 ID

※ Variables는 값이 노출돼도 되는 환경 설정, Secrets는 절대 공개되면 안 되는 민감 정보에만 사용합니다.


3. GitHub Actions YML 작성

.github/workflows/deploy.yml 파일 수정

우리는 기존 다른 분께서 운영 환경 배포 자동화를 해주었기 때문에, 그를 기반으로 개발 환경도 작성하였다.

name: Deploy Frontend to S3 & CloudFront

on:
    push:
        branches: [main, dev]

jobs:
    deploy-dev:
        if: github.ref == 'refs/heads/dev'
        runs-on: ubuntu-latest
        environment: dev
        env:
            # Environment variables (dev)
            VITE_KAKAO_JS_KEY: ${{ vars.VITE_KAKAO_JS_KEY }}
            VITE_GOOGLE_CLIENT_ID: ${{ vars.VITE_GOOGLE_CLIENT_ID }}
            VITE_APPLE_CLIENT_ID: ${{ vars.VITE_APPLE_CLIENT_ID }}
            VITE_APPLE_REDIRECT_URI: ${{ vars.VITE_APPLE_REDIRECT_URI }}
            VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL }}
            VITE_TOSS_PAYMENT_API_KEY: ${{ vars.VITE_TOSS_PAYMENT_API_KEY }}
            AWS_REGION: ${{ vars.AWS_REGION }}
            AWS_S3_BUCKET: ${{ vars.AWS_S3_BUCKET }}
            CF_DIST_ID: ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }}
            VITE_TOSS_PAYMENT_API_SECRET_KEY:
                ${{ secrets.VITE_TOSS_PAYMENT_API_SECRET_KEY }}
        steps:
            - uses: actions/checkout@v4
            - uses: actions/setup-node@v4
              with:
                  node-version: '22'
            - run: npm ci
            - run: npm run build
            - uses: aws-actions/configure-aws-credentials@v2
              with:
                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
                  aws-region: ${{ env.AWS_REGION }}
            - name: Deploy to S3 (dev)
              run: aws s3 sync ./dist s3://$AWS_S3_BUCKET --delete
            - name: Invalidate CloudFront (dev)
              run: |
                  aws cloudfront create-invalidation \
                    --distribution-id $CF_DIST_ID \
                    --paths "/*"

    deploy-prod:
        if: github.ref == 'refs/heads/main'
        runs-on: ubuntu-latest
        environment: prod
        env:
            # Environment variables (prod)
            VITE_KAKAO_JS_KEY: ${{ vars.VITE_KAKAO_JS_KEY }}
            VITE_GOOGLE_CLIENT_ID: ${{ vars.VITE_GOOGLE_CLIENT_ID }}
            VITE_APPLE_CLIENT_ID: ${{ vars.VITE_APPLE_CLIENT_ID }}
            VITE_APPLE_REDIRECT_URI: ${{ vars.VITE_APPLE_REDIRECT_URI }}
            VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL }}
            VITE_TOSS_PAYMENT_API_KEY: ${{ vars.VITE_TOSS_PAYMENT_API_KEY }}
            AWS_REGION: ${{ vars.AWS_REGION }}
            AWS_S3_BUCKET: ${{ vars.AWS_S3_BUCKET }}
            CF_DIST_ID: ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }}
            VITE_TOSS_PAYMENT_API_SECRET_KEY:
                ${{ secrets.VITE_TOSS_PAYMENT_API_SECRET_KEY }}
        steps:
            - uses: actions/checkout@v4
            - uses: actions/setup-node@v4
              with:
                  node-version: '22'
            - run: npm ci
            - run: npm run build
            - uses: aws-actions/configure-aws-credentials@v2
              with:
                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
                  aws-region: ${{ env.AWS_REGION }}
            - name: Deploy to S3 (prod)
              run: aws s3 sync ./dist s3://$AWS_S3_BUCKET --delete
            - name: Invalidate CloudFront (prod)
              run: |
                  aws cloudfront create-invalidation \
                    --distribution-id $CF_DIST_ID \
                    --paths "/*"

 


4. 동작 원리

  • dev 브랜치 push → deploy-dev Job 실행 → dev 환경 변수와 시크릿 사용
  • main 브랜치 push → deploy-prod Job 실행 → prod 환경 변수와 시크릿 사용
  • 빌드된 파일(dist)이 환경별 S3 버킷으로 업로드
  • CloudFront 캐시 무효화로 즉시 반영

 

5. 주의할 점 & 트러블슈팅

  • SPA 라우팅 404/403 처리를 꼭 해주자! (새로고침 시 404가 나온다면 꼭 확인)
  • IAM 키는 절대 공개하면 안 되며, 유출 시 바로 폐기
  • AWS Access Key 재발급 시, 기존 키를 사용하는 다른 배포/서비스에 영향이 갈 수 있으니 키 사용처를 반드시 확인 후 변경 → 나는 기존 키 발급자가 어떤 분인지 모르기도 했고, 기존 운영 서버에 문제가 생길 것을 방지하여 새로 발급받았다. (2개까지 발급 가능)

CloudFront 캐시 무효화 비용이 발생할 수 있으니, 빈번한 배포 시 주의


6. 테스트 방법

  1. dev 브랜치에서 간단한 UI 변경 후 git push origin dev
  2. GitHub Actions → Workflow 실행 확인
  3. CloudFront dev 도메인 접속 → 변경 사항 확인
  4. 같은 방식으로 main 브랜치에서 테스트
  5. 우리는 테스트용 토스페이먼츠 키가 별도로 존재하기 때문에 결제 연동 확인