Next.js로 개발한 사이드 프로젝트를 어떻게 배포하는게 좋을까? 🤔
모든 기술을 사용할 때 그렇듯이 각각의 방식이 장단점이 있다.
잘 정리되어 있는 기술 블로그가 있어 몇가지 방법 정리해보았다.
출처: https://dev.to/ethanleetech/best-8-deployment-and-hosting-for-nextjs-dip
Next.js 개발자들이 만든 플랫폼으로, Next.js 애플리케이션을 배포하기에 가장 적합한 선택
특징
요금: 무료 플랜 O, 유료 플랜은 월 $20부터 시작
적합한 대상: Next.js와의 통합 경험을 원하거나 뛰어난 성능을 필요로 하는 개발자
간단하면서도 강력한 기능을 제공하는 정적 및 동적 사이트 호스팅 플랫폼
특징
요금: 무료 플랜 O, 유료 플랜은 월 $19부터 시작
적합한 대상: 쉬운 배포와 서버리스 기능이 필요한 프로젝트
풀스택 애플리케이션 배포를 위한 종합적인 솔루션 제공
특징
요금: 사용량 기반 과금
적합한 대상: 광범위한 클라우드 서비스와 확장성을 요구하는 애플리케이션
간단함과 개발자 경험에 중점을 둔 클라우드 플랫폼으로, 정적 및 동적 호스팅을 모두 제공
특징
요금: 무료 플랜 제공, 유료 플랜은 $7/월부터 시작
적합한 대상: 단순하면서도 좋은 성능의 호스팅 솔루션을 원하는 개발자.
위 4가지 외에도 출처에서 소개하는건 DigitalOcean, Firebase Hosting, Heroku, Hostinger 가 있다.
추가로 내가 고려한 방법은
AWS에서 지원하는 가상 서버 서비스로, Next.js 애플리케이션을 실행하는데 필요한 모든 리소스를 직접 설정 가능
특징
요금: 프리티어 계정의 경우 월 750시간, EBS 30GB까지 무료, 유료는 사용량 기반 요금
적합한 대상: 대규모 프로젝트로 트래픽이 많거나 높은 확장성이 요구되는 경우, AWS 서비스를 통합적으로 활용하려는 경우
여러가지 방법 중에 내가 후보로 뽑은 두가지는 위 두가지 방식이다.
우선 Vercel의 경우 빠르고, 쉽고, 사이드 프로젝트 배포에 적합하다고 생각했다.
그리고 EC2는 이미 Express.js 서버가 EC2 인스턴스에 올라가 있었고, 이미지용 S3로 버킷도 있기 때문에 호스팅은 AWS 안에서 모두 처리하도록 구성해 통일성을 높이고 싶었다.
(AWS Amplify는 언제 과금될지 몰라 pass..)
| Vercel | AWS EC2 |
|---|---|
| 간편함: github와 연동해서 자동으로 배포 가능 | 복잡함: EC2를 구성하고 nginx, node.js, pm2 설정을 수동으로 해야 함 |
| 자동 빌드/배포: 브랜치 푸시 또는 PR 생성 시 자동으로 빌드 및 배포 | 수동 배포: 코드를 직접 업로드하거나 배포 파이프라인을 구성해야 함 |
| 가격: 소규모 애플리케이션은 무료 | 가격: 프리티어 한도 내에서 무료 |
| 스케일링: 트래픽 증가시 내부적으로 확장 | 스케일링: 별도로 구성해야 함 |
| ⭐️ 소켓 사용: 불가 | ⭐️ 소켓 사용: nginx 설정해서 가능 |
두 가지를 대략적으로 비교해보면 이렇다. 결론부터 말하면 EC2에 배포했는데 Vercel의 치명적인 단점은 소켓 사용이 불가하다..!
내가 배포하려는 애플리케이션은 SNS 사이드 프로젝트이기 때문에 실시간 채팅 서비스를 포함하고 있다.
그래서 몇개째인지 모를 AWS 프리티어 계정을 만들고
으로 클라이언트 배포 환경을 구성했다.
첫번째로 선택한건 Github Actions이다. 레포지토리와 통합이 간편하기도 하고 사용해보기도 했었고 무엇보다 ⭐️무료⭐️이다. (월 2000분까지)
name: Deploy to EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: SSH and deploy
env:
EC2_USER: ${{ secrets.EC2_USER }}
EC2_HOST: ${{ secrets.EC2_HOST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
echo "$SSH_PRIVATE_KEY" > private_key.pem
chmod 600 private_key.pem
ssh -o StrictHostKeyChecking=no -i private_key.pem -T $EC2_USER@$EC2_HOST << 'EOF'
cd /home/ubuntu/SNS_Project
git pull origin main
npm install
npm run build
pm2 restart sns_client || pm2 start npm --name sns_client -- start
EOF
rm private_key.pem
shell: bash
이렇게 yaml 파일을 작성했는데
위 과정으로 CI/CD를 구성했다.
🤔 근데 뭔가 아쉬운데?
프로덕션 환경 EC2 환경에서 직접 빌드하는 것보단 빌드된 결과물만 보내서 실행하는게 낫지 않나?
하는 의문이 들었다. 보안상의 문제도 존재하고, 서버 빌드는 CPU, 메모리도 많이 차지하고 빌드 시간도 늘어난다.
그럼 이럴 때 쓰는건 뭐다?

🐳 도커다! (아님)
물론 도커의 장점도 있지만 별 고민없이 EC2 인스턴스에 도커를 설치하고 Next.js 애플리케이션을 컨테이너화 시켰다. 참고 링크
| 장점 | 단점 |
|---|---|
| 일관된 환경 및 이식성 | 초기 설정의 복잡성 |
| 간소화된 종속성 관리 | 이미지 크기 관리 |
| 경량화 | 리소스 사용량 증가 |
| 보안 | 로컬 디버깅 어려움 |
결과적으로는 사용하지 않았지만 그래도 과정을 정리해보자면 아래와 같다.
# Base stage---------------------------
FROM node:20 AS base
RUN npm install -g pnpm
# Build stage---------------------------
FROM base AS builder
WORKDIR /app
# 종속성 파일 복사 및 설치
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
# Production Stage---------------------------
FROM node:20-alpine AS runner
# 작업 디렉토리 설정
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup -S nodejs && adduser -S -H -D -G nodejs nextjs
# 빌드 결과물과 필요한 파일만 복사
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
# 환경 변수 설정 (예: 포트)
EXPOSE 3000
# 애플리케이션 시작
CMD ["node", "server.js"]
services:
app:
build:
context: .
dockerfile: Dockerfile
environment:
- PORT=3000
- NEXT_PUBLIC_PROD_API_URL=${NEXT_PUBLIC_PROD_API_URL}
env_file:
- .env
expose:
- "3000" # 내부적으로 Nginx와 통신하기 위해 expose 사용
networks:
- webnet
nginx:
image: nginx:latest
volumes:
- /etc/nginx/conf.d/sns.jwoo.site.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt:/etc/letsencrypt
ports:
- "80:80"
- "443:443"
depends_on:
- app
networks:
- webnet
networks:
webnet:
driver: bridge
name: Deploy to EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: SSH and deploy with Docker
env:
EC2_USER: ${{ secrets.EC2_USER }}
EC2_HOST: ${{ secrets.EC2_HOST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
echo "$SSH_PRIVATE_KEY" > private_key.pem
chmod 600 private_key.pem
ssh -o StrictHostKeyChecking=no -i private_key.pem -T $EC2_USER@$EC2_HOST << 'EOF'
cd /home/ubuntu/SNS_Project
git pull origin main
docker-compose pull app # app 서비스의 이미지를 최신으로 가져옴
docker-compose up -d --build # 변경된 내용을 적용하며 컨테이너를 빌드 및 재시작
docker image prune -f # 불필요한 이미지를 삭제해 디스크 공간 확보
EOF
rm private_key.pem
shell: bash
근데 우여곡절 끝에 막상 도커를 적용하고 나니까 드는 의문은 꼭 도커가 필요할까..??
파이프라인이 실행되는 시간도 약 3m~4m로 Next.js 애플리케이션의 크기에 비해 길다고 생각했고 EC2 프리티어 인스턴스 기준으로 docker가 적지 않은 용량을 차지한다.
그래서 어떤 경우에 Next.js에 도커를 적용하는지 찾아본 결과 흥미로운 영상을 보았다.
Next.js + Docker가 make sense, 즉 납득 가능한 조합이냐는건데, 결론적으로 말하면 도커로 구성된 다른 애플리케이션들이 있는 경우 납득 가능한 조합이라는 것이다.
그게 아니라 Next.js 애플리케이션 하나만 배포하려고 한다면 자신은 도커 캡틴이지만 그냥 Vercel을 사용하라고 할 것이라고 한다.
팀원이 많거나, 애플리케이션이 여러 개라던가, MSA 환경이라던가 하면 도커로 관리되고 있는 개발 환경에 Next.js가 이미지화되어 추가되는 것은 dockerize하는 것의 이점을 충분히 얻을 수 있지만, 그렇지 않다면 단점이 더 크다.
따라서 소규모 프로젝트이면서 리소스가 제한된 환경인 Next.js 1인 사이드 프로젝트에서는 도커가 적합하지 않은 기술 도입이라고 결론을 내렸다.
여기까지의 교훈은...
기술을 도입할 때는 충분히 알아보고 납득가는 근거가 있을 때 실행하자..(남들 다 쓴다고 무작정 따라하지 말자)
블로그 게시물로 정리하니까 간단해보이지만 시행착오 시간이 꽤 있었고, 이게 실무에서의 상황이라면 시간=돈이기 때문에 나는 돈을 날린 것이 아닌가 🥲
이후 Github Actions + S3 + CodeDeploy로 구성된 CI/CD로 다시 구축했고, 그 과정은 다음 포스팅에 이어서!