
안녕하세요! 이번 포스팅에서는 AWS의 S3와 CloudFront를 활용하여 정적 웹사이트부터 React, Next.js 애플리케이션까지 다양한 프론트엔드 프로젝트를 배포하는 방법을 알아보겠습니다.
S3는 AWS에서 제공하는 파일 저장 서비스입니다. 단순히 파일을 저장하는 것뿐만 아니라, 정적 웹사이트 호스팅 기능을 제공하여 HTML, CSS, JavaScript 파일을 호스팅할 수 있습니다. 이는 프론트엔드 개발자에게 매우 유용한 기능입니다.
CloudFront는, 한 마디로 정의하면 콘텐츠(파일, 동영상 등)를 빠르게 전송해주는 서비스입니다. 이는 CDN(Content Delivery Network)의 일종으로, 전 세계 여러 지역에 콘텐츠의 복사본을 저장해두고 사용자와 가장 가까운 위치에서 콘텐츠를 제공합니다.
S3만으로도 웹사이트 호스팅이 가능하지만, CloudFront를 함께 사용하면 다음과 같은 이점이 있습니다:
S3에서는 몇 가지 특별한 용어를 사용합니다:
my-frontend-websiteap-northeast-2 (서울)버킷에 파일을 업로드했다고 해서 바로 외부에서 접근할 수 있는 것은 아닙니다. 퍼블릭 액세스를 허용하는 정책을 추가해야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
index.html 입력error.html 또는 SPA의 경우 index.html 입력프론트엔드 프로젝트를 자동으로 S3에 업로드하려면 프로그래밍 방식으로 S3에 접근할 수 있는 권한이 필요합니다. 이를 위해 IAM(Identity and Access Management)에서 액세스 키를 발급받아야 합니다.
s3-upload-user)AmazonS3FullAccess 정책 선택your-bucket-name.s3-website.ap-northeast-2.amazonaws.comindex.html 입력CloudFront 배포가 생성되면 배포 도메인 이름(예: d1a2b3c4d5e6f7.cloudfront.net)이 제공됩니다. 이를 통해 웹사이트에 접근할 수 있습니다.
CloudFront에 사용할 인증서는 반드시 미국 동부(버지니아 북부) 리전에서 발급받아야 합니다.
example.com)*.example.com)www.example.com)React 애플리케이션을 S3와 CloudFront에 배포하는 과정은 일반 정적 웹사이트와 유사하지만, 몇 가지 추가 설정이 필요합니다.
# 프로젝트 빌드
npm run build
# 결과물은 build 폴더에 생성됩니다
aws s3 sync build/ s3://your-bucket-name
React 애플리케이션을 업데이트할 때마다 CloudFront 캐시를 무효화해야 변경 사항이 즉시 반영됩니다.
/* (모든 파일 무효화)React Router를 사용하는 SPA(Single Page Application)의 경우, S3 정적 웹사이트 호스팅에서 오류 문서를 index.html로 설정하고, CloudFront에서 사용자 지정 오류 응답을 구성해야 합니다.
/index.htmlNext.js는 정적 생성(Static Generation)과 서버 사이드 렌더링(Server-Side Rendering)을 모두 지원합니다. 배포 방식은 어떤 기능을 사용하느냐에 따라 달라집니다.
Next.js를 완전한 정적 사이트로 내보내는 경우, S3와 CloudFront를 사용할 수 있습니다.
next.config.js 파일에 정적 내보내기 설정 추가module.exports = {
output: 'export',
}
npm run build
# 결과물은 out 폴더에 생성됩니다
out 폴더 내용 업로드aws s3 sync out/ s3://your-bucket-name
aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths "/*"
Next.js의 SSR, API 라우트, 미들웨어 등의 기능을 사용하는 경우, S3와 CloudFront만으로는 배포할 수 없습니다. 이 경우 다음과 같은 대안이 있습니다:
AWS Amplify: Next.js 애플리케이션을 쉽게 배포할 수 있는 AWS 서비스
# Amplify CLI 설치
npm install -g @aws-amplify/cli
# Amplify 초기화
amplify init
# Amplify 호스팅 추가
amplify add hosting
# 배포
amplify publish
Vercel: Next.js를 개발한 회사에서 제공하는 호스팅 서비스
# Vercel CLI 설치
npm i -g vercel
# 배포
vercel
EC2 + Nginx: AWS EC2 인스턴스에 직접 Next.js 서버 설치 및 Nginx로 프록시
Docker를 사용하면 애플리케이션과 그 환경을 컨테이너화하여 어디서든 일관되게 실행할 수 있습니다. 프론트엔드 개발자로서 Docker를 활용하면 다음과 같은 장점이 있습니다:
프로젝트 루트 디렉토리에 Dockerfile을 생성합니다:
# 빌드 단계
FROM node:16-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 프로덕션 단계
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
# React Router를 위한 Nginx 설정
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
프로젝트 루트에 nginx 폴더를 만들고, 그 안에 nginx.conf 파일을 생성합니다:
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
Next.js의 경우, SSR을 지원하므로 조금 다른 Dockerfile이 필요합니다:
# 기본 이미지
FROM node:16-alpine
# 작업 디렉토리 설정
WORKDIR /app
# 의존성 파일 복사 및 설치
COPY package*.json ./
RUN npm ci
# 소스 코드 복사
COPY . .
# 애플리케이션 빌드
RUN npm run build
# 포트 설정
EXPOSE 3000
# 시작 명령어
CMD ["npm", "start"]
여러 서비스를 함께 실행해야 하는 경우(예: 프론트엔드 + 백엔드 API), Docker Compose를 사용하면 편리합니다.
프로젝트 루트에 docker-compose.yml 파일을 생성합니다:
version: '3'
services:
frontend:
build: .
ports:
- "80:80"
# 백엔드 API와 통신해야 하는 경우
environment:
- REACT_APP_API_URL=http://api:8080
# volumes:
# - ./src:/app/src # 개발 중 소스 코드 변경 실시간 반영 (개발 환경에서만 사용)
# 백엔드 API가 있는 경우
api:
image: your-backend-image
ports:
- "8080:8080"
Docker 이미지를 AWS ECR(Elastic Container Registry)에 저장하고 ECS(Elastic Container Service)나 EC2에서 실행할 수 있습니다.
frontend-app)# AWS CLI를 사용하여 ECR에 로그인
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin YOUR_AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com
# Docker 이미지 빌드
docker build -t frontend-app .
# 이미지에 ECR 리포지토리 태그 추가
docker tag frontend-app:latest YOUR_AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com/frontend-app:latest
# ECR에 이미지 푸시
docker push YOUR_AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com/frontend-app:latest
ECR에 푸시한 Docker 이미지를 ECS를 사용하여 배포할 수 있습니다.
frontendYOUR_AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com/frontend-app:latest)서버 관리 없이 컨테이너를 실행하려면 AWS Fargate를 사용할 수 있습니다.
frontendFargate나 ECS에 배포한 Docker 컨테이너 앞에 CloudFront를 두고 Route 53으로 도메인을 연결할 수 있습니다.
Application Load Balancer 생성
CloudFront 배포 생성
Route 53에서 도메인 연결
프론트엔드 배포를 자동화하면 개발 효율성이 크게 향상됩니다. GitHub Actions를 사용하여 S3, CloudFront 또는 Docker 배포를 자동화해 보겠습니다.
.github/workflows/deploy-s3.yml 파일 생성name: Deploy to S3 and CloudFront
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to S3
uses: jakejarvis/s3-sync-action@master
with:
args: --follow-symlinks --delete
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: 'ap-northeast-2'
SOURCE_DIR: 'build' # React는 'build', Next.js는 'out'
- name: Invalidate CloudFront
uses: chetan/invalidate-cloudfront-action@master
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 }}
.github/workflows/deploy-docker.yml 파일 생성name: Deploy Docker to AWS
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: frontend-app
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: frontend
image: ${{ steps.login-ecr.outputs.registry }}/frontend-app:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: frontend-service
cluster: frontend-cluster
wait-for-service-stability: true
task-definition.json 파일 생성 (ECS 작업 정의){
"family": "frontend-task",
"executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole",
"networkMode": "awsvpc",
"containerDefinitions": [
{
"name": "frontend",
"image": "YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com/frontend-app:latest",
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/frontend-task",
"awslogs-region": "ap-northeast-2",
"awslogs-stream-prefix": "ecs"
}
}
}
],
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "256",
"memory": "512"
}
AWS_S3_BUCKET: S3 버킷 이름AWS_ACCESS_KEY_ID: IAM 액세스 키 IDAWS_SECRET_ACCESS_KEY: IAM 비밀 액세스 키CLOUDFRONT_DISTRIBUTION_ID: CloudFront 배포 ID프로젝트가 끝나면 불필요한 비용이 발생하지 않도록 리소스를 정리해야 합니다.
버킷 비우기: 모든 객체를 삭제해야 버킷을 삭제할 수 있습니다.
버킷 삭제하기
CloudFront 배포 비활성화
CloudFront 배포 삭제하기
CloudFront에서 사용 중인 인증서는 삭제할 수 없으므로, CloudFront 배포를 먼저 삭제해야 합니다.
이 글에서는 프론트엔드 프로젝트를 배포하는 다양한 방법을 알아보았습니다
S3와 CloudFront를 사용한 정적 웹사이트 배포
Docker를 활용한 컨테이너화 배포
CI/CD 파이프라인 구축
각 배포 방식은 장단점이 있으며, 프로젝트의 특성과 요구사항에 따라 적절한 방법을 선택해야 합니다