React 앱 Docker 이미지로 배포하기

yong·2025년 9월 25일
post-thumbnail

1. Docker 이미지 생성

1-1. Dockerfile 작성

Dockerfile
docker에서 이미지를 생성하기 위한 용도로 작성하는 파일로, 만들 이미지에 대한 정보를 기술해 둔 템플릿

프로젝트의 루트 디렉토리에 다음과 같이 Dockerfile을 만든다. 이때 멀티 스테이지 빌드(다단계 빌드) 방식을 사용하여 빌드 환경과 실행 환경을 분리함으로써 이미지 크기를 최적화한다.

# STEP 1: 빌드 환경 설정 및 프로젝트 빌드
FROM node:20-alpine AS build-stage
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

# STEP 2: 프로덕션 환경을 위한 Nginx 설정
FROM nginx:1.23-alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

현재 프로젝트에서 빌드 도구로 vite를 사용하고 있기 때문에 기본적으로 빌드 결과물이 /dist 디렉터리에 생성된다. 따라서 /app/dist 폴더를 nginx가 정적 파일을 찾는 경로로 복사해준다.
⚠️ CRA는 빌드 결과물이 /build 디렉터리에 생성되므로 혼동하지 말 것!

1-2. Nginx 설정 파일 작성

Reat 앱은 SPA(Single Page Application)이기 때문에 Nginx 설정을 통해 모든 경로의 요청을 index.html로 리디렉션해야 한다. 이 설정 또한 루트 디렉토리의 nginx.conf 파일에 해준다.

server {
  listen 3000;

  # Nginx가 정적 파일을 찾을 기본 경로를 명시적으로 설정
  root /usr/share/nginx/html;

  # 모든 요청을 처리하는 location 블록
  location / {
      # 요청된 URI($uri)가 존재하지 않으면, /index.html을 반환하여 SPA 라우팅을 지원
      try_files $uri $uri/ /index.html;
  }

  # 정적 파일에 대한 캐시 설정 (선택 사항)
  # 캐시 기간을 1년으로 설정하여 성능 최적화
  location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
    expires 1y;
    access_log off;
    add_header Cache-Control "public";
  }

  # gzip 압축 설정 (파일 전송 용량 최적화)
  gzip on;
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

}

✨ 처음에는 nginx 설정을 아래와 같이 해서 배포된 앱에 접속했을 때 사진과 같이 흰 화면만 뜨는 이슈가 있었는데, 이는 웹서버의 root 경로 설정을 location 블록 밖에 해줌으로써 해결할 수 있었다.
⇒ 이렇게 하면 server 블록 내의 모든 location 블록이 동일한 root 경로를 상속받게 되어 Nginx가 모든 파일을 위에서 지정한 build 결과물이 위치한 /usr/share/nginx/html에서 찾을 수 있게 된다.

server {
    listen 3000;

    # SPA(Single Page Application) 라우팅을 위한 설정
    # 모든 요청을 index.html로 리디렉션
    location / {
        root   /usr/share/nginx/html; # root 경로 설정 위치에 유의
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    # 중략 ...
}

1-3. Docker 이미지 생성

프로젝트의 루트 디렉토리에서 Dockerfile과 nginx.conf 파일을 생성 후 docker build 명령을 통해 이미지를 생성한다. 이때 태그를 지정하지 않으면 기본적으로 latest 태그가 붙는다.

# docker build [옵션] 이미지이름[:태그] Dockerfile의_디렉토리경로
docker build -t alcha-frontend .

2. 이미지를 푸시할 ECR 레포지토리 생성

ECR 레포지토리를 만들고 콘솔에서 aws 계정으로 로그인하여 레포지토리에 이미지를 푸시한다.

2-1. ECR 레포지토리 생성

2-2. ECR 로그인

Docker CLI로 docker 레지스트리에 이미지를 푸시하기 위해서는 docker CLI가 레지스트리(여기서는 ECR)에 대한 접근 권한을 가지고 있어야 한다. 따라서 aws ecr get-login-password 명령을 통해 ECR에 접속할 수 있는 임시 비밀번호(토큰)을 생성하고 이를 docker login 명령어에 넘겨줘서 원하는 레지스트리(<your-aws-account-id>.dkr.ecr.ap-northeast-2.amazonaws.com)에 로그인한다.

# AWS CLI 인증 및 docker 레지스트리 로그인
aws ecr get-login-password --region <your-region> | docker login --username AWS --password-stdin <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com



2-3. ECR에 이미지 push

레지스트리에 이미지를 등록할 때는 레지스트리/레포지토리/이미지이름:태그 형태가 되어야 하므로 이미지의 태그를 수정하고 push해야 한다.

(이미지 레지스트리로 Docker Hub를 지정하는 경우에는 레지스트리 부분을 생략하고 레포지토리는 자신이 등록한 name으로 작성)

# ECR에 이미지를 푸시하기 위해 이미지 태그 수정
# docker tag 원본이미지이름[:버전] 참조이미지이름[:버전]
docker tag <your-image-name>:latest <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/<your-repository-name>:latest

# 이미지 이름에 지정된  push
docker push <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/<your-repository-name>:latest

3. EC2에서 이미지 pull 후 실행

docker pull <aws-account-id>.dkr.ecr.ap-northeast-2.amazonaws.com/alcha/frontend:v1.0.5

docker run -p 5179:3000 -d <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/<your-repository-name>:latest

이때 ec2의 인바운드 설정으로 포트를 열어주어야 접속이 가능하다.

EC2의 공인IP 주소로 접속 성공!

Dockerfile의 EXPOSE와 docker run -p 명령에서 지정하는 포트의 차이

  • EXPOSE
    EXPOSE는 컨테이너의 내부 포트 정보를 문서화하는 역할로, EXPOSE 80은 "이 컨테이너가 80번 포트를 사용해서 서비스를 제공할 거야"라고 다른 개발자나 시스템에게 알려주는 것이다. 하지만 이 명령어 자체만으로는 실제로 포트를 개방하거나 네트워크 연결을 만들지 않는다.
  • docker run -p
    docker run -p <호스트 포트>:<컨테이너 포트> 명령은 호스트(EC2)와 컨테이너 사이의 포트를 매핑하여 실제 통신 경로를 만든다.
    ⇒ 따라서 Dockerfile에서 지정한 80번 포트는 무시되고 3000번 포트만 노출되어 컨테이너는 3000번 포트에서 실행된다. 이 포트는 Nginx 설정 파일에 지정한 listen 포트(3000)와 일치해야 3000번 포트를 listen하고 있는 웹서버가 5173으로 노출되어 외부에서 접근이 가능해진다.

이 과정을 진행하면서 이미지 빌드 및 배포를 적어도 10번은 했는데 너무 번거로웠다.🤕 CI/CD 구축의 필요성을 제대로 느껴서 'CI/CD 구축기'로 돌아오겠다.

profile
꿈틀꿈틀

0개의 댓글