Next.js를 AWS EC2에 무중단으로 배포하기

Next.js를 EC2에 무중단 배포하는 방법은 여러 가지가 있는데, 규모와 복잡도 따라 추천이 달라져요. 크게 3가지 접근을 설명하고, 실무에서 가장 많이 쓰는 Blue-Green + Nginx 방식을 정리했습니다.

PORT 3000 · ACTIVE
PORT 3001 · STANDBY
→ 헬스체크 통과 후 Nginx가 이 둘을 서로 바꿔치기합니다.

무중단 배포의 핵심은 간단합니다. 새 버전을 완전히 띄운 뒤, 준비가 끝난 것을 확인하고 나서 트래픽을 넘긴다. EC2 위에서 이걸 구현하는 방법은 크게 세 가지가 있는데, 규모에 따라 선택지가 달라집니다.

방식 난이도 무중단 보장 적합한 규모
PM2 cluster reload 낮음 준수함 (완벽 X) 단일 인스턴스, 소규모
Blue-Green + Nginx 중간 확실함 단일~소수 인스턴스
CodeDeploy + ALB Blue-Green 높음 확실함 Auto Scaling Group, 다중 인스턴스

  • PM2 reload: 같은 서버 안에서 워커를 하나씩 순차 재시작. 설정이 제일 간단하지만, 빌드 산출물 자체가 깨지거나 새 코드가 실행 중 프로세스와 충돌하면 순간 에러가 날 수 있어요.
  • Blue-Green + Nginx: 포트 두 개(3000/3001)를 번갈아 쓰면서 Nginx가 트래픽을 스위칭. 롤백도 config 한 줄만 되돌리면 되니 실무에서 가장 균형 잡힌 선택이에요.
  • CodeDeploy + ALB: 인스턴스가 여러 대거나 Auto Scaling을 쓴다면 AWS 네이티브 기능으로 가는 게 맞아요. 다만 설정이 더 무겁습니다.

인스턴스가 1~2대 규모라면 Blue-Green + Nginx가 가장 균형 잡힌 선택입니다. 설정이 크게 무겁지 않으면서도, 롤백이 config 한 줄로 끝난다는 장점이 있습니다.

배포 흐름

구체적인 구현

아래 순서대로 구성하면 됩니다.

디렉터리 구조 (EC2)

릴리스마다 폴더를 분리하고 symlink로 현재 버전을 가리키게 합니다.

/home/ubuntu/app/
  ├── releases/
  │   ├── 20260714120000/
  │   └── 20260714150000/
  ├── current -> releases/20260714150000  (symlink)
  └── shared/.env

PM2 ecosystem 설정

포트 두 개를 각각 별도 PM2 프로세스로 등록합니다.

ecosystem.config.js
module.exports = {
  apps: [
    { name: "next-blue",  script: "npm", args: "start", env: { PORT: 3000 } },
    { name: "next-green", script: "npm", args: "start", env: { PORT: 3001 } },
  ],
};

Nginx 설정

배포 스크립트가 이 upstream 줄만 바꿔치기합니다.

/etc/nginx/conf.d/upstream.conf
upstream nextjs_upstream {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    location / {
        proxy_pass http://nextjs_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

배포 스크립트

현재 활성 포트를 감지하고, 반대쪽 포트에 새 버전을 띄운 뒤 헬스체크를 통과하면 전환합니다.

deploy.sh
#!/bin/bash
set -e

CURRENT_PORT=$(grep -oP '127.0.0.1:\K[0-9]+' /etc/nginx/conf.d/upstream.conf)
if [ "$CURRENT_PORT" == "3000" ]; then
  NEW_PORT=3001; NEW_APP="next-green"; OLD_APP="next-blue"
else
  NEW_PORT=3000; NEW_APP="next-blue"; OLD_APP="next-green"
fi

echo "새 버전을 포트 $NEW_PORT 에 배포합니다."
cd /home/ubuntu/app/current
pm2 restart $NEW_APP --update-env

# 헬스체크 (최대 30초 대기)
for i in {1..15}; do
  if curl -sf http://127.0.0.1:$NEW_PORT/api/health > /dev/null; then
    echo "헬스체크 통과"
    break
  fi
  sleep 2
done

# Nginx upstream 전환
sudo sed -i "s/127.0.0.1:[0-9]\+/127.0.0.1:$NEW_PORT/" /etc/nginx/conf.d/upstream.conf
sudo nginx -s reload

# 구버전 종료
pm2 stop $OLD_APP
echo "배포 완료: 활성 포트 $NEW_PORT"

GitHub Actions 워크플로우

main 브랜치에 push되면 빌드 후 EC2로 전송하고 배포 스크립트를 실행합니다.

.github/workflows/deploy.yml
name: Deploy to EC2
on:
  push:
    branches: [main]

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

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npm run build

      - name: Rsync build output to EC2
        uses: burnett01/[email protected]
        with:
          switches: -avzr --delete
          path: ./
          remote_path: /home/ubuntu/app/releases/${{ github.sha }}/
          remote_host: ${{ secrets.EC2_HOST }}
          remote_user: ubuntu
          remote_key: ${{ secrets.EC2_SSH_KEY }}

      - name: Run zero-downtime deploy script
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            ln -sfn /home/ubuntu/app/releases/${{ github.sha }} /home/ubuntu/app/current
            bash /home/ubuntu/app/current/deploy.sh

핵심 포인트

헬스체크 API를 반드시 두세요. /api/health가 없으면 아직 부팅 중인 새 버전으로 트래픽이 넘어가 순간 에러가 날 수 있어요.
DB 마이그레이션에 주의하세요. 파괴적 변경은 expand-contract 패턴으로 나눠서, 구버전과 신버전이 동시에 떠 있는 순간에도 문제가 없도록 해야 합니다.
롤백은 스위칭 로직만 반대로 실행하면 됩니다. 문제가 생기면 매우 빠르게 되돌릴 수 있어요.
인스턴스가 여러 대라면 ALB + CodeDeploy로. Target Group 두 개(blue/green)를 두고 CodeDeploy가 전환하는 방식이 더 적합합니다.

#Nextjs #AWS #EC2 #CICD #무중단배포 #BlueGreen배포 #GitHubActions #Nginx #PM2 #DevOps

댓글

이 블로그의 인기 게시물

Claude Code 토큰 절약 가이드

Codex로 무료 온라인 툴 사이트 만들기: AGENTS.md 하네스 적용부터 직접 따라 하기

‘클라우드플레어(Cloudflare)’란 무엇일까? 내 블로그를 빠르고 안전하게! (무료 기능 및 티스토리 연동법)