이미지 로딩 중...

Vite CI/CD 파이프라인 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 25. · 5 Views

Vite CI/CD 파이프라인 완벽 가이드

GitHub Actions부터 Docker 컨테이너화, 그리고 Vercel/Netlify 자동 배포까지! Vite 프로젝트를 실무 환경에 자동으로 배포하는 전체 과정을 초급 개발자도 쉽게 따라할 수 있도록 단계별로 알려드립니다.


목차

  1. GitHub Actions에서 Vite 빌드
  2. Vercel/Netlify 자동 배포 설정
  3. Docker 컨테이너화
  4. Nginx 정적 파일 서빙 최적화
  5. CDN 배포 전략
  6. 환경별 배포 자동화

1. GitHub Actions에서 Vite 빌드

시작하며

여러분이 코드를 수정하고 GitHub에 푸시할 때마다 직접 서버에 접속해서 빌드하고 배포하는 과정을 반복하고 계신가요? 팀원이 여러 명이면 누군가 빌드를 깜빡해서 문제가 생기기도 하죠.

이런 문제는 실제 개발 현장에서 정말 자주 발생합니다. 사람이 직접 하는 작업은 실수가 생기기 마련이고, 시간도 많이 걸립니다.

특히 여러 명이 협업할 때는 누가 언제 배포했는지 추적하기도 어렵습니다. 바로 이럴 때 필요한 것이 GitHub Actions 자동 빌드입니다.

코드를 푸시하면 자동으로 테스트하고 빌드해주는 로봇 비서를 두는 것과 같습니다.

개요

간단히 말해서, GitHub Actions는 여러분의 GitHub 저장소에서 특정 이벤트(푸시, PR 등)가 발생하면 자동으로 작업을 실행해주는 자동화 도구입니다. 왜 이것이 필요할까요?

Vite 프로젝트는 배포 전에 반드시 빌드 과정을 거쳐야 합니다. 개발 중에는 vite dev 명령어로 개발 서버를 띄우지만, 실제 서비스에는 vite build로 최적화된 파일을 만들어야 하죠.

이 과정을 매번 수동으로 하면 시간도 오래 걸리고 실수하기도 쉽습니다. 기존에는 로컬 컴퓨터에서 빌드하고 FTP로 업로드하거나 직접 서버에 접속해서 빌드했다면, 이제는 코드만 푸시하면 자동으로 모든 과정이 진행됩니다.

GitHub Actions의 핵심 특징은 첫째, YAML 파일로 워크플로우를 정의하고, 둘째, GitHub에서 제공하는 무료 실행 환경을 사용하며, 셋째, 실패하면 즉시 알림을 받을 수 있다는 점입니다. 이러한 특징들이 실무에서는 배포 안정성을 크게 높여줍니다.

코드 예제

# .github/workflows/build.yml
name: Vite Build CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # 1. 코드 가져오기
      - uses: actions/checkout@v3

      # 2. Node.js 설치
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      # 3. 의존성 설치
      - name: Install dependencies
        run: npm ci

      # 4. Vite 빌드 실행
      - name: Build with Vite
        run: npm run build

      # 5. 빌드 결과물 업로드
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/

설명

이것이 하는 일: 위 워크플로우 파일은 여러분이 main이나 develop 브랜치에 코드를 푸시하거나 풀 리퀘스트를 생성할 때 자동으로 실행됩니다. 마치 배달 주문을 하면 자동으로 요리가 시작되는 것처럼요.

첫 번째로, on 섹션은 "언제 이 작업을 실행할지"를 정의합니다. push와 pull_request 이벤트에 반응하도록 설정했기 때문에, 코드를 푸시하거나 PR을 만들면 즉시 GitHub Actions가 작동합니다.

왜 이렇게 하냐면, 버그가 있는 코드가 메인 브랜치에 합쳐지기 전에 미리 발견할 수 있기 때문입니다. 그 다음으로, steps 부분이 실행되면서 순차적으로 작업이 진행됩니다.

checkout 액션으로 코드를 가져오고, Node.js를 설치하고, npm ci로 정확히 package-lock.json에 명시된 버전의 패키지들을 설치합니다. 여기서 npm install이 아닌 npm ci를 쓰는 이유는 CI 환경에서 더 빠르고 안정적이기 때문입니다.

마지막으로, npm run build가 실행되어 Vite가 dist 폴더에 최적화된 파일들을 생성합니다. 이 결과물은 upload-artifact 액션으로 저장되어 나중에 다른 작업에서 사용하거나 직접 다운로드할 수 있습니다.

여러분이 이 코드를 사용하면 더 이상 "내 컴퓨터에서는 되는데요?"라는 말을 할 필요가 없습니다. 모든 빌드가 동일한 환경에서 실행되니까요.

또한 빌드가 실패하면 즉시 이메일이나 Slack으로 알림을 받을 수 있어, 문제를 빠르게 발견하고 해결할 수 있습니다.

실전 팁

💡 cache: 'npm'을 설정하면 node_modules를 캐싱해서 빌드 속도가 2-3배 빨라집니다. 매번 모든 패키지를 다시 다운로드하지 않아도 되거든요.

💡 빌드 실패 시 Slack 알림을 받고 싶다면 slack-notify 액션을 추가하세요. 팀 전체가 즉시 문제를 인지할 수 있습니다.

💡 여러 Node.js 버전에서 테스트하려면 strategy.matrix를 사용하세요. [14, 16, 18] 버전에서 동시에 빌드할 수 있습니다.

💡 .env 파일의 환경 변수는 GitHub Secrets에 저장하고 ${{ secrets.VITE_API_KEY }} 형태로 주입하세요. 절대 코드에 직접 넣지 마세요!

💡 PR에 자동으로 빌드 결과를 코멘트로 남기고 싶다면 actions/github-script를 활용하세요. 번들 크기 변화를 한눈에 볼 수 있습니다.


2. Vercel/Netlify 자동 배포 설정

시작하며

여러분이 빌드한 파일을 직접 서버에 업로드하거나 S3에 올리는 작업을 반복하고 계신가요? 도메인 설정, HTTPS 인증서, CDN 설정까지 직접 해야 한다면 정말 복잡하고 시간이 오래 걸립니다.

이런 문제는 특히 프론트엔드 개발자들에게 큰 부담입니다. 우리는 코드를 작성하는 데 집중하고 싶은데, 인프라 설정에 시간을 너무 많이 쓰게 되죠.

잘못 설정하면 사이트가 느려지거나 아예 접속이 안 되기도 합니다. 바로 이럴 때 필요한 것이 Vercel이나 Netlify 같은 배포 플랫폼입니다.

GitHub 저장소만 연결하면 푸시할 때마다 자동으로 빌드하고 배포하며, HTTPS와 CDN까지 모두 자동으로 설정해줍니다.

개요

간단히 말해서, Vercel과 Netlify는 프론트엔드 프로젝트를 위한 원클릭 배포 플랫폼입니다. 복잡한 서버 설정 없이 Git 저장소만 연결하면 끝입니다.

왜 이것이 필요한가요? Vite로 빌드한 정적 파일들은 어딘가에 호스팅되어야 사용자들이 접근할 수 있습니다.

전통적으로는 Nginx 서버를 직접 구축하거나 S3 + CloudFront 조합을 사용했는데, 이런 방식은 설정이 복잡하고 유지보수가 어렵습니다. 예를 들어, 새 버전을 배포할 때마다 캐시를 무효화하고 배포 상태를 확인하는 작업이 필요했죠.

기존에는 FTP로 파일을 업로드하거나 AWS CLI로 S3에 sync 명령을 실행했다면, 이제는 git push만 하면 자동으로 전 세계 CDN에 배포됩니다. Vercel의 핵심 특징은 첫째, Vite를 자동으로 인식하고 최적화하며, 둘째, 브랜치마다 고유한 프리뷰 URL을 제공하고, 셋째, 엣지 네트워크를 통해 전 세계에서 빠르게 접근할 수 있다는 점입니다.

Netlify도 유사하지만 폼 처리나 서버리스 함수 같은 추가 기능이 강점입니다. 이러한 특징들이 개발자 경험을 크게 향상시켜줍니다.

코드 예제

# vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "devCommand": "npm run dev",
  "installCommand": "npm install",
  "framework": "vite",
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

설명

이것이 하는 일: 위 설정 파일은 Vercel에게 "어떻게 빌드하고 배포할지"를 알려줍니다. 마치 요리 레시피를 주는 것처럼, Vercel은 이 지시사항을 따라 여러분의 앱을 배포합니다.

첫 번째로, buildCommand와 outputDirectory는 가장 기본적인 설정입니다. "npm run build를 실행하고, 결과물은 dist 폴더에 있어"라고 알려주는 거죠.

framework를 "vite"로 지정하면 Vercel이 Vite에 최적화된 설정을 자동으로 적용합니다. 왜 이렇게 하냐면, Vite는 특별한 빌드 최적화와 환경 변수 처리 방식이 있기 때문입니다.

그 다음으로, rewrites 설정이 매우 중요합니다. SPA(Single Page Application)는 모든 경로가 실제로는 index.html을 보여줘야 하는데, 이 설정이 없으면 /about 같은 경로에서 404 에러가 발생합니다.

이 규칙은 "모든 요청을 index.html로 보내라"는 의미이고, React Router나 Vue Router 같은 클라이언트 라우팅이 작동하게 해줍니다. 마지막으로, headers 설정은 성능 최적화를 담당합니다.

/assets/ 폴더의 파일들(JS, CSS, 이미지 등)은 내용이 변경되면 파일명도 바뀌기 때문에, 브라우저가 1년 동안 캐시해도 안전합니다. max-age=31536000과 immutable을 설정하면 사용자들이 두 번째 방문부터는 파일을 다시 다운로드하지 않아 페이지가 훨씬 빠르게 로드됩니다.

여러분이 이 설정을 사용하면 배포 시간이 수 분에서 수 초로 단축되고, PR마다 고유한 프리뷰 URL이 생겨서 팀원들이 바로 확인할 수 있습니다. 또한 롤백도 클릭 한 번이면 되고, A/B 테스트나 카나리 배포 같은 고급 기능도 쉽게 사용할 수 있습니다.

실전 팁

💡 환경 변수는 Vercel 대시보드에서 Production/Preview/Development 환경별로 따로 설정하세요. 개발 환경과 운영 환경의 API 엔드포인트가 다를 때 유용합니다.

💡 vercel --prod 명령어로 CLI에서도 배포할 수 있습니다. CI/CD 파이프라인에 통합할 때 활용하세요.

💡 Netlify를 사용한다면 netlify.toml 파일에 [[redirects]] 섹션으로 동일하게 설정할 수 있습니다. 문법만 약간 다를 뿐입니다.

💡 Build & Development Settings에서 Node.js 버전을 명시적으로 지정하세요. 로컬과 다른 버전이면 빌드가 실패할 수 있습니다.

💡 Vercel의 Analytics를 활성화하면 Core Web Vitals를 실시간으로 모니터링할 수 있습니다. 사용자 경험 개선에 필수적입니다.


3. Docker 컨테이너화

시작하며

여러분이 개발한 앱을 동료의 컴퓨터나 서버에 배포했을 때 "내 컴퓨터에서는 되는데요?"라는 말을 들어본 적 있나요? Node.js 버전이 다르거나, 환경 변수가 누락되거나, 운영체제가 달라서 생기는 문제들이죠.

이런 문제는 실제 개발 현장에서 정말 골치 아픈 이슈입니다. 개발 환경과 운영 환경이 다르면 예상치 못한 버그가 발생하고, 원인을 찾는 데만 몇 시간씩 낭비하게 됩니다.

특히 여러 마이크로서비스를 운영하면 각각의 환경을 맞추는 것이 거의 불가능해집니다. 바로 이럴 때 필요한 것이 Docker 컨테이너화입니다.

앱과 그 앱이 필요한 모든 것(Node.js, 환경 변수, 설정 파일)을 하나의 박스에 담아서, 어디서든 똑같이 실행되도록 만드는 기술입니다.

개요

간단히 말해서, Docker는 여러분의 앱을 "컨테이너"라는 독립된 실행 환경에 패키징하는 도구입니다. 마치 이사할 때 짐을 박스에 담듯이, 앱을 컨테이너에 담는 거죠.

왜 이것이 필요한가요? Vite로 빌드한 정적 파일들은 결국 웹 서버를 통해 제공되어야 합니다.

개발할 때는 vite preview로 간단히 테스트할 수 있지만, 운영 환경에서는 Nginx 같은 고성능 웹 서버가 필요합니다. Docker를 사용하면 Nginx 설정, 빌드 파일, 모든 의존성을 하나의 이미지로 만들어서 어디서든 동일하게 실행할 수 있습니다.

예를 들어, AWS, GCP, Azure 어디든 같은 Docker 이미지를 사용할 수 있죠. 기존에는 서버마다 Nginx를 설치하고 설정 파일을 복사하고 권한을 설정했다면, 이제는 docker run 명령어 하나면 끝입니다.

Docker의 핵심 특징은 첫째, 멀티 스테이지 빌드로 이미지 크기를 최소화하고, 둘째, 레이어 캐싱으로 빌드 속도를 높이며, 셋째, 어디서든 동일하게 실행된다는 점입니다. 이러한 특징들이 배포의 일관성과 안정성을 보장합니다.

코드 예제

# Dockerfile
# 스테이지 1: 빌드 환경
FROM node:18-alpine AS builder

WORKDIR /app

# 의존성 파일만 먼저 복사 (캐싱 최적화)
COPY package*.json ./
RUN npm ci

# 소스 코드 복사 및 빌드
COPY . .
RUN npm run build

# 스테이지 2: 운영 환경
FROM nginx:alpine

# Nginx 설정 복사
COPY nginx.conf /etc/nginx/nginx.conf

# 빌드 결과물만 복사
COPY --from=builder /app/dist /usr/share/nginx/html

# 포트 노출
EXPOSE 80

# Nginx 실행
CMD ["nginx", "-g", "daemon off;"]

설명

이것이 하는 일: 위 Dockerfile은 두 단계로 나뉘어 있습니다. 첫 번째 단계에서는 Node.js로 앱을 빌드하고, 두 번째 단계에서는 Nginx로 빌드 결과물만 서빙합니다.

마치 요리할 때는 큰 주방이 필요하지만, 서빙할 때는 작은 접시만 있으면 되는 것과 같습니다. 첫 번째로, builder 스테이지는 node:18-alpine 이미지를 기반으로 합니다.

alpine은 매우 가벼운 리눅스 배포판으로, 일반 node 이미지(약 900MB)에 비해 alpine 버전은 약 120MB밖에 안 됩니다. package*.json을 먼저 복사하고 npm ci를 실행하는 이유는, 소스 코드가 변경되어도 package.json이 안 바뀌었으면 이 레이어를 캐시에서 재사용할 수 있기 때문입니다.

이렇게 하면 빌드 시간이 5분에서 30초로 단축될 수 있습니다. 그 다음으로, 실제 소스 코드를 복사하고 npm run build를 실행합니다.

이 시점에서 Vite가 dist 폴더에 최적화된 HTML, CSS, JS 파일들을 생성합니다. 여기까지가 builder 스테이지의 역할이고, 이제 이 무거운 Node.js 환경은 버립니다.

마지막으로, 두 번째 스테이지에서는 nginx:alpine을 사용합니다. COPY --from=builder 명령어로 첫 번째 스테이지의 dist 폴더만 가져옵니다.

Node.js, npm, node_modules 같은 빌드 도구들은 최종 이미지에 포함되지 않아서, 최종 이미지 크기가 약 20-30MB로 매우 작아집니다. Nginx는 정적 파일 서빙에 최적화되어 있어서 초당 수만 건의 요청도 처리할 수 있습니다.

여러분이 이 Dockerfile을 사용하면 로컬에서 docker build -t my-vite-app .으로 이미지를 만들고, docker run -p 8080:80 my-vite-app으로 어디서든 실행할 수 있습니다. AWS ECS, Kubernetes, Google Cloud Run 어디든 같은 이미지를 배포할 수 있어, 환경 차이로 인한 문제가 완전히 사라집니다.

실전 팁

💡 .dockerignore 파일에 node_modules, dist, .git을 추가하세요. 빌드 컨텍스트 크기가 줄어들어 이미지 빌드가 훨씬 빠릅니다.

💡 멀티 플랫폼 빌드를 위해 docker buildx build --platform linux/amd64,linux/arm64를 사용하세요. M1 Mac에서 만든 이미지도 인텔 서버에서 실행됩니다.

💡 환경 변수는 Dockerfile에 직접 넣지 말고 docker run -e VITE_API_URL=https://api.example.com 형태로 주입하세요.

💡 이미지 크기를 더 줄이고 싶다면 nginx:alpine 대신 busybox 또는 scratch 이미지에 정적 파일 서버를 직접 빌드할 수도 있습니다.

💡 Docker Compose로 개발 환경을 구성하면 팀원들이 docker-compose up 한 줄로 전체 환경을 실행할 수 있습니다.


4. Nginx 정적 파일 서빙 최적화

시작하며

여러분의 Vite 앱을 배포했는데 페이지 로딩이 느리거나, 새로고침하면 404 에러가 나거나, 이미지가 제대로 캐싱되지 않는 경험을 해보셨나요? 이런 문제들은 대부분 웹 서버 설정이 제대로 안 되어서 발생합니다.

이런 문제는 실제 서비스 운영에서 사용자 경험을 크게 해칩니다. 페이지가 느리면 사용자들이 떠나가고, 404 에러는 SEO에도 악영향을 미치며, 캐싱이 안 되면 서버 비용도 증가합니다.

특히 트래픽이 많아지면 이런 문제들이 더욱 심각해집니다. 바로 이럴 때 필요한 것이 Nginx 최적화 설정입니다.

Gzip 압축, 캐시 헤더, SPA 라우팅 처리, 보안 헤더까지 모두 Nginx 설정 파일 하나로 해결할 수 있습니다.

개요

간단히 말해서, Nginx는 정적 파일을 서빙하는 데 특화된 고성능 웹 서버입니다. Apache보다 메모리를 적게 사용하면서 더 많은 동시 연결을 처리할 수 있습니다.

왜 이것이 필요한가요? Vite로 빌드하면 HTML, CSS, JS, 이미지 파일들이 생성되는데, 이것들을 그냥 서빙하는 것과 최적화해서 서빙하는 것은 성능 차이가 엄청납니다.

예를 들어, Gzip 압축만 켜도 전송 데이터가 70% 정도 줄어들고, 올바른 캐시 헤더를 설정하면 재방문자의 페이지 로딩 시간이 10배 이상 빨라집니다. 또한 SPA는 클라이언트 라우팅을 사용하기 때문에, /about 같은 경로로 직접 접속하거나 새로고침하면 404 에러가 나는데, Nginx에서 이를 처리해야 합니다.

기존에는 각 설정을 인터넷에서 찾아서 하나씩 적용했다면, 이제는 검증된 최적화 설정을 한 번에 적용할 수 있습니다. Nginx의 핵심 최적화 요소는 첫째, Gzip 압축으로 전송량 감소, 둘째, 캐시 헤더로 재요청 방지, 셋째, try_files로 SPA 라우팅 처리, 넷째, 보안 헤더로 XSS 방어입니다.

이러한 설정들이 성능과 보안을 동시에 향상시킵니다.

코드 예제

# nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # Gzip 압축 활성화
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;

    # SPA 라우팅 처리 (모든 요청을 index.html로)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 정적 자산 캐싱 (해시가 포함된 파일명)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 보안 헤더
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

설명

이것이 하는 일: 위 Nginx 설정은 Vite 앱을 최적으로 서빙하기 위한 모든 필수 설정을 포함합니다. 마치 자동차에 터보 엔진을 다는 것처럼, 같은 앱이지만 훨씬 빠르게 작동합니다.

첫 번째로, gzip 설정은 텍스트 기반 파일들을 압축해서 전송합니다. 예를 들어, 500KB짜리 JavaScript 파일이 Gzip으로 압축되면 약 150KB로 줄어듭니다.

gzip_types에 명시된 파일 타입만 압축되고, gzip_min_length는 1000바이트 이하의 작은 파일은 압축하지 않습니다(오히려 오버헤드가 생길 수 있어서). 왜 이게 중요하냐면, 모바일 네트워크 환경에서 전송량이 줄어들면 페이지 로딩이 몇 초씩 빨라지기 때문입니다.

그 다음으로, try_files $uri $uri/ /index.html은 SPA의 핵심 설정입니다. 사용자가 /about으로 접속하면, Nginx는 먼저 /about이라는 파일이 있는지 확인하고(없음), /about/ 디렉토리가 있는지 확인하고(없음), 마지막으로 /index.html을 반환합니다.

그러면 React Router나 Vue Router 같은 클라이언트 라우터가 /about 경로를 처리합니다. 이 설정이 없으면 SPA에서 새로고침하거나 직접 URL로 접속할 때마다 404 에러가 발생합니다.

마지막으로, 정적 자산 캐싱과 보안 헤더를 설정합니다. Vite는 빌드할 때 파일명에 해시를 붙입니다(예: app.a3f2b9.js).

이런 파일들은 내용이 변경되면 파일명도 바뀌기 때문에, expires 1y로 1년 동안 캐시해도 안전합니다. immutable 속성은 브라우저에게 "이 파일은 절대 변경되지 않으니 재검증도 하지 마"라고 알려줍니다.

보안 헤더들은 XSS 공격, 클릭재킹, MIME 타입 스니핑 같은 보안 위협을 방어합니다. 여러분이 이 설정을 사용하면 Lighthouse 성능 점수가 70점대에서 95점 이상으로 올라가고, 재방문자들은 페이지가 거의 즉시 로드되는 경험을 하게 됩니다.

또한 서버 대역폭 사용량이 줄어들어 비용도 절감됩니다.

실전 팁

💡 Brotli 압축을 추가하면 Gzip보다 15-20% 더 작은 파일 크기를 얻을 수 있습니다. ngx_brotli 모듈을 설치하고 brotli on을 추가하세요.

💡 HTTP/2를 활성화하면 여러 파일을 동시에 전송할 수 있어 성능이 크게 향상됩니다. listen 443 ssl http2로 설정하세요.

💡 access_log를 버퍼링하면 디스크 I/O가 줄어듭니다. access_log /var/log/nginx/access.log buffer=32k flush=5s 형태로 설정하세요.

💡 API 프록시가 필요하면 location /api { proxy_pass http://backend:3000; }로 백엔드 서버로 요청을 전달할 수 있습니다. CORS 문제도 해결됩니다.

💡 rate limiting으로 DDoS 공격을 방어하세요. limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s로 IP당 초당 요청 수를 제한할 수 있습니다.


5. CDN 배포 전략

시작하며

여러분의 앱을 한국에서 배포했는데, 미국이나 유럽 사용자들이 "사이트가 너무 느려요"라고 불평한 경험이 있나요? 물리적 거리가 멀수록 데이터 전송 시간이 길어지는 것은 피할 수 없는 현실입니다.

이런 문제는 글로벌 서비스를 운영할 때 반드시 해결해야 하는 과제입니다. 서울 서버에서 뉴욕까지 데이터가 전달되는 데 200ms 이상 걸리고, 이것이 모든 요청마다 반복되면 사용자 경험이 형편없어집니다.

또한 트래픽이 몰리면 하나의 서버로는 감당하기 어렵습니다. 바로 이럴 때 필요한 것이 CDN(Content Delivery Network) 배포 전략입니다.

전 세계 수백 개의 서버에 여러분의 파일을 복사해두고, 사용자와 가장 가까운 서버에서 파일을 제공하는 방식입니다.

개요

간단히 말해서, CDN은 여러분의 정적 파일들을 전 세계 여러 위치에 캐싱해서, 사용자가 가장 가까운 서버에서 빠르게 다운로드할 수 있게 하는 네트워크입니다. 왜 이것이 필요한가요?

Vite로 빌드한 파일들은 대부분 정적이고 자주 변경되지 않습니다. 이런 파일들을 원본 서버에서만 제공하면 전 세계 사용자들이 모두 멀리 떨어진 서버에 접속해야 하죠.

CDN을 사용하면 서울 사용자는 서울 엣지 서버에서, 뉴욕 사용자는 뉴욕 엣지 서버에서 파일을 받게 됩니다. 예를 들어, CloudFront를 사용하면 전 세계 450개 이상의 엣지 로케이션에서 콘텐츠를 제공할 수 있어, 평균 응답 시간이 50ms 이하로 줄어듭니다.

기존에는 지역별로 서버를 직접 구축하거나 단일 서버로 모든 트래픽을 처리했다면, 이제는 CDN이 자동으로 전 세계에 콘텐츠를 분산시켜 줍니다. CDN의 핵심 특징은 첫째, 지리적으로 가까운 서버에서 제공하여 레이턴시 감소, 둘째, 캐시 적중률을 높여 원본 서버 부하 감소, 셋째, DDoS 공격을 자동으로 완화, 넷째, HTTPS와 HTTP/2를 자동 지원한다는 점입니다.

이러한 특징들이 성능과 안정성을 동시에 향상시킵니다.

코드 예제

# GitHub Actions에서 S3 + CloudFront 배포
name: Deploy to CDN

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install and Build
        run: |
          npm ci
          npm run build

      # S3에 업로드
      - name: Upload to S3
        uses: jakejarvis/s3-sync-action@master
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: 'us-east-1'
          SOURCE_DIR: 'dist'

      # CloudFront 캐시 무효화
      - name: Invalidate CloudFront
        uses: chetan/invalidate-cloudfront-action@v2
        env:
          DISTRIBUTION: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
          PATHS: '/*'
          AWS_REGION: 'us-east-1'
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

설명

이것이 하는 일: 위 워크플로우는 Vite 앱을 S3에 업로드하고 CloudFront CDN을 통해 전 세계에 배포하는 전체 과정을 자동화합니다. 마치 책을 출판하면 전국 서점에 자동으로 배포되는 것과 같습니다.

첫 번째로, 빌드 과정은 이전과 동일합니다. npm ci로 의존성을 설치하고 npm run build로 dist 폴더에 최적화된 파일들을 생성합니다.

여기까지는 로컬에서 하는 것과 똑같죠. 왜 이 과정을 자동화하냐면, 사람이 직접 하면 깜빡하거나 잘못된 버전을 배포할 수 있기 때문입니다.

그 다음으로, S3 업로드가 진행됩니다. jakejarvis/s3-sync-action은 dist 폴더의 모든 파일을 S3 버킷에 동기화합니다.

이미 존재하는 파일은 변경된 것만 업데이트하고, 삭제된 파일은 S3에서도 삭제합니다. 환경 변수로 AWS 자격 증명을 전달하는데, 이것들은 GitHub Secrets에 안전하게 저장되어 있어야 합니다.

S3는 스토리지이고 CloudFront가 실제로 사용자에게 파일을 제공하는 CDN입니다. 마지막으로, CloudFront 캐시 무효화가 매우 중요합니다.

새 버전을 배포해도 CDN에 이전 버전이 캐시되어 있으면 사용자들은 여전히 오래된 파일을 보게 됩니다. PATHS: '/*'는 모든 파일의 캐시를 무효화하라는 의미입니다.

이 작업이 완료되면(보통 1-2분), 전 세계 모든 엣지 서버가 새 버전을 제공하기 시작합니다. 여러분이 이 설정을 사용하면 코드를 푸시한 후 5분 이내에 전 세계 사용자들이 새 버전을 사용할 수 있습니다.

또한 CloudFront의 분석 대시보드에서 지역별 트래픽, 캐시 적중률, 오류율 등을 모니터링할 수 있어, 문제를 빠르게 발견하고 해결할 수 있습니다.

실전 팁

💡 index.html은 캐시하지 않고(Cache-Control: no-cache), JS/CSS는 1년 캐시하세요. index.html이 최신 버전의 해시된 파일명을 가리키도록 해야 합니다.

💡 CloudFront Functions나 Lambda@Edge로 A/B 테스트를 구현할 수 있습니다. 엣지에서 트래픽을 분산시켜 지연 없이 실험할 수 있죠.

💡 Origin Shield를 활성화하면 원본 서버 보호와 캐시 효율이 더 좋아집니다. 추가 비용은 거의 없는데 효과는 큽니다.

💡 S3 버전 관리를 켜두면 실수로 파일을 삭제해도 복구할 수 있고, 이전 버전으로 롤백도 쉽습니다.

💡 CloudFlare를 대안으로 사용하면 무료 플랜에서도 무제한 대역폭과 자동 DDoS 방어를 제공합니다. 설정도 더 간단합니다.


6. 환경별 배포 자동화

시작하며

여러분이 개발 중인 기능을 운영 환경에 바로 배포했다가 서비스 전체가 다운된 경험이 있나요? 아니면 여러 환경(개발, 스테이징, 운영)을 관리하면서 어느 환경에 어떤 버전이 배포되어 있는지 헷갈린 적이 있나요?

이런 문제는 실제 서비스 운영에서 정말 치명적입니다. 테스트되지 않은 코드가 운영에 배포되면 수많은 사용자들이 영향을 받고, 긴급 롤백이 필요해집니다.

또한 환경마다 다른 API 엔드포인트, 데이터베이스, 인증 설정을 관리하는 것도 복잡합니다. 바로 이럴 때 필요한 것이 환경별 배포 자동화입니다.

develop 브랜치는 자동으로 개발 환경에, main 브랜치는 운영 환경에 배포되고, 각 환경별로 다른 환경 변수가 자동으로 적용되도록 만드는 것입니다.

개요

간단히 말해서, 환경별 배포 자동화는 Git 브랜치 전략과 CI/CD를 결합하여, 각 브랜치가 자동으로 해당하는 환경에 배포되도록 하는 시스템입니다. 왜 이것이 필요한가요?

실무에서는 보통 여러 환경을 운영합니다. 개발(Development) 환경은 개발자들이 자유롭게 테스트하고, 스테이징(Staging) 환경은 QA 팀이 최종 검증하며, 운영(Production) 환경은 실제 사용자들이 사용합니다.

각 환경마다 API 서버 주소, 데이터베이스, 로깅 레벨, 기능 플래그 등이 다르죠. 예를 들어, 개발 환경에서는 https://dev-api.example.com을, 운영에서는 https://api.example.com을 사용해야 합니다.

이런 설정을 수동으로 관리하면 실수하기 쉽습니다. 기존에는 배포할 때마다 .env 파일을 수동으로 바꾸거나, 환경 변수를 하나씩 설정했다면, 이제는 브랜치만 머지하면 자동으로 올바른 환경에 올바른 설정으로 배포됩니다.

환경별 배포의 핵심 특징은 첫째, Git 브랜치와 환경을 1:1로 매핑, 둘째, 환경별 환경 변수를 CI/CD 도구에서 안전하게 관리, 셋째, 배포 승인 프로세스로 운영 환경 보호, 넷째, 자동 롤백으로 빠른 장애 복구입니다. 이러한 특징들이 배포의 안정성과 추적 가능성을 보장합니다.

코드 예제

# .github/workflows/deploy-by-environment.yml
name: Environment-based Deployment

on:
  push:
    branches:
      - develop    # 개발 환경
      - staging    # 스테이징 환경
      - main       # 운영 환경

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      # 환경 결정
      - name: Set environment
        id: set-env
        run: |
          if [[ ${{ github.ref }} == 'refs/heads/main' ]]; then
            echo "env_name=production" >> $GITHUB_OUTPUT
            echo "api_url=${{ secrets.PROD_API_URL }}" >> $GITHUB_OUTPUT
          elif [[ ${{ github.ref }} == 'refs/heads/staging' ]]; then
            echo "env_name=staging" >> $GITHUB_OUTPUT
            echo "api_url=${{ secrets.STAGING_API_URL }}" >> $GITHUB_OUTPUT
          else
            echo "env_name=development" >> $GITHUB_OUTPUT
            echo "api_url=${{ secrets.DEV_API_URL }}" >> $GITHUB_OUTPUT
          fi

      # 환경 변수 주입하여 빌드
      - name: Build with environment
        run: |
          npm ci
          VITE_API_URL=${{ steps.set-env.outputs.api_url }} \
          VITE_ENV=${{ steps.set-env.outputs.env_name }} \
          npm run build

      # 환경별 배포
      - name: Deploy to ${{ steps.set-env.outputs.env_name }}
        run: |
          echo "Deploying to ${{ steps.set-env.outputs.env_name }}"
          # 실제 배포 명령어 (Vercel, Netlify, S3 등)

설명

이것이 하는 일: 위 워크플로우는 어느 브랜치에 푸시했는지 감지하고, 자동으로 해당 환경의 설정을 적용하여 빌드하고 배포합니다. 마치 택배를 보낼 때 주소에 따라 자동으로 다른 경로로 배송되는 것과 같습니다.

첫 번째로, on.push.branches에 세 개의 브랜치를 지정했습니다. develop 브랜치에 푸시하면 개발 환경으로, staging은 스테이징으로, main은 운영 환경으로 배포됩니다.

이런 브랜치 전략을 Git Flow라고 하는데, 실무에서 가장 많이 사용됩니다. 왜 이렇게 하냐면, 각 브랜치가 명확한 역할을 가지고 있어서 코드의 안정성을 단계별로 검증할 수 있기 때문입니다.

그 다음으로, Set environment 단계가 매우 중요합니다. ${{ github.ref }}로 현재 브랜치를 확인하고, 조건문으로 환경을 결정합니다.

각 환경마다 다른 API URL을 GitHub Secrets에서 가져오는데, 예를 들어 PROD_API_URL은 https://api.example.com이고 DEV_API_URL은 https://dev-api.example.com입니다. 이 값들은 $GITHUB_OUTPUT에 저장되어 다음 단계에서 사용할 수 있습니다.

민감한 정보를 코드에 직접 넣지 않고 Secrets에 저장하는 것은 보안의 기본입니다. 마지막으로, 빌드 단계에서 환경 변수를 주입합니다.

Vite는 VITE_ 접두사가 붙은 환경 변수만 클라이언트 코드에 포함시킵니다. VITE_API_URL과 VITE_ENV를 설정하면, 여러분의 코드에서 import.meta.env.VITE_API_URL로 접근할 수 있습니다.

이렇게 빌드된 dist 폴더는 해당 환경에 맞게 설정된 버전이므로, 잘못된 환경에 배포할 걱정이 없습니다. 여러분이 이 설정을 사용하면 개발자는 feature 브랜치에서 작업하고 develop에 머지하면 자동으로 개발 환경에 배포되어 즉시 테스트할 수 있습니다.

QA 팀이 승인하면 staging으로 머지되어 스테이징 환경에서 최종 검증하고, 문제없으면 main에 머지하여 운영에 배포합니다. 각 단계마다 자동화되어 있어 사람의 실수가 들어갈 여지가 없습니다.

실전 팁

💡 운영 배포는 수동 승인(manual approval)을 추가하세요. environment: production과 required_reviewers를 설정하면 승인 없이 배포되지 않습니다.

💡 .env.development, .env.staging, .env.production 파일을 만들어 로컬에서도 환경별로 테스트할 수 있습니다.

💡 배포 후 자동으로 Slack이나 Discord에 알림을 보내세요. 팀 전체가 배포 상태를 실시간으로 파악할 수 있습니다.

💡 Sentry나 DataDog 같은 모니터링 도구에 환경별로 다른 프로젝트를 연결하세요. 운영 환경의 에러가 개발 환경과 섞이지 않습니다.

💡 feature 플래그 시스템(LaunchDarkly, Unleash)을 도입하면 코드는 배포하되 기능은 나중에 켤 수 있습니다. 배포와 릴리스를 분리하는 고급 기법입니다.


#Vite#GitHub Actions#Docker#CI/CD#Deployment#Vite,빌드도구,프론트엔드,DevOps

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

WebSocket과 Server-Sent Events 실시간 통신 완벽 가이드

웹 애플리케이션에서 실시간 데이터 통신을 구현하는 핵심 기술인 WebSocket과 Server-Sent Events를 다룹니다. 채팅, 알림, 실시간 업데이트 등 현대 웹 서비스의 필수 기능을 구현하는 방법을 배워봅니다.

API 테스트 전략과 자동화 완벽 가이드

API 개발에서 필수적인 테스트 전략을 단계별로 알아봅니다. 단위 테스트부터 부하 테스트까지, 실무에서 바로 적용할 수 있는 자동화 기법을 익혀보세요.

효과적인 API 문서 작성법 완벽 가이드

API 문서는 개발자와 개발자 사이의 가장 중요한 소통 수단입니다. 이 가이드에서는 좋은 API 문서가 갖춰야 할 조건부터 Getting Started, 엔드포인트 설명, 에러 코드 문서화, 인증 가이드, 변경 이력 관리까지 체계적으로 배워봅니다.

API 캐싱과 성능 최적화 완벽 가이드

웹 서비스의 응답 속도를 획기적으로 개선하는 캐싱 전략과 성능 최적화 기법을 다룹니다. HTTP 캐싱부터 Redis, 데이터베이스 최적화, CDN까지 실무에서 바로 적용할 수 있는 핵심 기술을 초급자 눈높이에서 설명합니다.

OAuth 2.0과 소셜 로그인 완벽 가이드

OAuth 2.0의 핵심 개념부터 구글, 카카오 소셜 로그인 구현까지 초급 개발자를 위해 쉽게 설명합니다. 인증과 인가의 차이점, 다양한 Flow의 특징, 그리고 보안 고려사항까지 실무에 바로 적용할 수 있는 내용을 다룹니다.