React와 Docker, Docker Container

sham·2025년 1월 6일
0

개요

React는 어떻게 build 되는가?

vite든, CRA든 번들링 도구를 사용하면 익숙하게 보게 될 package.json의 코드이다.

vite 실행 시 Vite 개발 서버를 로컬에서 실행하여 해당 브라우저에서 JS 파일을 요청하고, 우리는 그 결과물을 localhost 상의 포트에서 확인할 수 있다.

"scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
    "test": "jest",
    "prettier": "prettier --write --config ./.prettierrc ./src"
  },

Vercel 배포 과정

그렇다면, 배포를 위해 build할 때는 어떻게 동작하는가? Vercel에 레포지토리를 연결하기만 해도 동작하는 이유가 무엇일까?

Vercel에서 배포할 때 찍히는 로그들을 분석해보자.

// 프로덕션 빌드가 시작
// 설정 파일(vite.config.js)을 읽어 빌드 프로세스를 초기화
vite v5.4.11 building for production... 

// ESBuild를 사용해 소스 파일과 종속성을 빠르게 분석하고 변환.
// TypeScript, JSX, Vue/Svelte 등의 비표준 파일이 표준 JavaScript로 컴파일
transforming...
node_modules/lottie-web/build/player/lottie.js (17010:32): Use of eval in "node_modules/lottie-web/build/player/lottie.js" is strongly discouraged as it poses security risks and may cause issues with minification.
✓ 3202 modules transformed.

// Rollup을 사용해 코드 스플리팅과 번들링을 수행
// 코드를 여러 청크(chunk)로 분리. 번들링 결과는 dist/assets에 저장
rendering chunks...

//  번들 파일의 압축 전, 압축 후 크기를 계산해 표시
computing gzip size...
dist/index.html                   0.95 kB │ gzip:   0.52 kB
dist/assets/index-CbGtfgdF.css    4.96 kB │ gzip:   1.26 kB
dist/assets/index-CGoU96VL.css  232.44 kB │ gzip:  31.00 kB
dist/assets/index--tBCjIru.js     0.83 kB │ gzip:   0.52 kB
dist/assets/index-C-ukZz5N.js     0.88 kB │ gzip:   0.55 kB
dist/assets/index-DRr-MHVL.js     1.25 kB │ gzip:   0.60 kB
dist/assets/index-c_RGvYKk.js   288.30 kB │ gzip:  81.85 kB
dist/assets/index-L_1_6mqk.js   446.61 kB │ gzip: 102.42 kB
dist/assets/index-Bvohxkgz.js   811.24 kB │ gzip: 226.20 kB
✓ built in 12.14s

// 일부 청크가 크다며 경고
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

// 빌드 완료, dist에 결과물 생성
Done in 19.49s.
Build Completed in /vercel/output [50s]

// 빌드된 파일을 Vercel 서버에 업로드
Deploying outputs...
Deployment completed
Uploading build cache [66.54 MB]...
Build cache uploaded: 881.425ms

Vercel은 서버가 아니다

Vercel을 쓰면 아주 편리하지만, 이런 호스팅 배포 사이트에 모든 서비스를 맡길 수는 없다. Google이나 Naver 까지가 아니더라도 일반적인 서비스라면 AWS 같은 유료 서비스, 정확히는 S3와 같은 서버를 하나 장만해서 배포를 하게 될 것이다.

그렇다면 배포를 할 때마다 vite build 명령을 해서 나온 폴더를 AWS 서버에 매번 옮겨줄 것인가? 그것은 너무나도 불편하고 비효율적이다. 게다가 개발할 때는 잘 되던 기능이 배포된 환경에서는 동작하지 않을 위험도 있다.

배포 과정과 실행 환경을 표준화할 수 있는 기술이 있다면 일관된 실행 환경을 보장할 수도 있고, 배포 과정 역시 자동화할 수 있다. 이를 가능케 하는 것이 Docker이다.

Docker가 뭔데?

Docker는 애플리케이션을 실행하는 데 필요한 모든 환경(코드, 라이브러리, 설정 파일 등)을 하나의 패키지(컨테이너)로 묶어주는 컨테이너 기반 가상화 플랫폼이다.

docker 의 용도는 애플리케이션을 환경에 구애받지 않고 실행하기 위해서라고 할 수 있다. 윈도우와 맥 그리고 리눅스, 리눅스에서도 데비안 알파인... 명령어에서 부터 시작해서 별의 별것이 다르다. 하지만 도커는 이 모든걸 한번 작성해 놓으면 어디서든지 동일한 코드로 빌드할 수 있다.

이 컨테이너는 애플리케이션과 그에 필요한 라이브러리, 종속성들을 하나의 패키지로 묶어 다른 환경에서도 일관되게 동작하도록 보장해준다. 이로 인해 개발 환경과 실제 배포 환경 간의 차이로 인해 발생하는 문제를 줄일 수 있다.

Docker를 쓰면 뭐가 좋지?

그럼 가상 머신과 다를게 없지 않나? 하고 생각할 수 있을 것이다. 가상화 플랫폼을 제공한다는 것은 동일하니 말이다.

하지만 다르다. 명백히 다르다. Docker가 제공할 수 있는 장점들이 있다.

  • 환경 일관성 제공
    • 내 로컬에서는 잘 되는데, 서버에서 안 되는 문제를 해결.
    • 운영체제와 무관하게 동일한 환경을 보장.
  • 효율적인 리소스 사용
    • 가상머신(VM)과 비교했을 때 별도의 운영체제를 포함한 무거운 이미지를 사용하는 VM과 다르게, Docker는 호스트 운영체제를 공유하는 가벼운 컨테이너를 사용.
    • VM보다 빠르고 가벼우며 리소스 소모가 적음. → 비용 절감!
  • 빠른 배포 및 확장성
    • Docker 이미지를 통해 애플리케이션을 몇 초 안에 배포 가능.
  • 버전 관리 및 롤백 가능
    • Docker 이미지는 Immutable(불변)한 특성을 가지므로 특정 버전의 이미지를 저장하고 필요 시 과거 버전으로 롤백 가능.
    • 배포 및 수정 이력 관리가 쉬움.
  • 이식성 (Portability)
    • Docker 이미지를 사용하면 동일한 애플리케이션을 어디서든 실행 가능:
      • 로컬 개발 환경
      • 테스트 서버
      • 클라우드 서비스 (AWS, GCP, Azure 등)
    • 클라우드 플랫폼에 의존하지 않고 자유롭게 이동 가능.

본문

용어 정리

Docker는 여러 구성 요소로 이루어져 있으며, 이들 각각이 협력하여 컨테이너 기반 가상화 환경을 제공한다.

Docker 클라이언트 (Docker Client)

  • 사용자와 Docker 데몬 간의 인터페이스. 사용자는 터미널에서 docker 명령어를 입력해 Docker 데몬에게 명령을 전달하고, 데몬은 이를 실행한다.
  • 주요 명령어 : docker run, docker build, docker pull, docker push 등.

Docker HOST

  • Docker가 띄워져있는 서버를 의미한다. 이곳에서 Docker 데몬이 컨테이너와 이미지를 관리한다.

Docker 데몬 (Docker Daemon)

  • 컨테이너와 이미지를 관리하는 백그라운드 프로세스이자 docker 엔진. dockerd라는 프로세스를 통해 실행되며, Docker API 요청을 처리하고 컨테이너를 실행, 중지, 삭제하는 등의 작업을 수행한다.
  • 클라이언트에서 명령을 보내면, 데몬이 이를 처리하여 필요한 작업을 수행합니다.

Docker 이미지 (Docker Image)

  • 애플리케이션과 그 실행에 필요한 모든 파일, 라이브러리, 종속성을 포함한 읽기 전용 템플릿. 컨테이너는 이 이미지를 기반으로 생성된다.
  • 여러 레이어로 구성되어 있으며, 이미지를 변경할 때마다 새로운 레이어가 추가된다. 이미지는 Docker Hub와 같은 레지스트리에 저장할 수 있다.

Docker 컨테이너 (Docker Container)

  • 이미지를 실행한 가상화된 실행 환경. 하나의 컨테이너는 독립적인 프로세스로서, 고립된 상태에서 애플리케이션을 실행한다.
  • 컨테이너는 가볍고 빠르며, 필요에 따라 쉽게 생성, 복제, 삭제할 수 있다.

Docker 레지스트리 (Docker Registry)

  • Docker 이미지를 저장하고 배포하는 외부 이미지 저장소 역할을 한다. 개발자는 이미지를 Docker 레지스트리에 푸시(push)하고, 다른 사용자나 시스템은 이 이미지를 풀(pull)하여 사용할 수 있습니다.
  • Docker Hub - 가장 널리 사용되는 공개 레지스트리. Docker Hub, QUAY
  • 프라이빗 레지스트리 - 사내나 조직 내부에서만 사용되는 비공개 레지스트리. AWS나 직접 Docker 레지스트리를 띄우는 방식으로 구축한다.

Docker Compose

  • 여러 컨테이너로 이루어진 애플리케이션을 정의하고 실행하기 위한 도구. YAML 파일(docker-compose.yml)을 사용해 여러 서비스를 정의하고, 한 번에 실행할 수 있다.
  • 복잡한 멀티 컨테이너 애플리케이션을 손쉽게 관리하고, 개발 및 테스트 환경에서 자주 사용된다.

Docker 네트워크 (Docker Network)

  • 컨테이너들 간의 통신을 관리한다. Docker는 여러 종류의 네트워크 드라이버를 제공한다.
    • 브리지(bridge): 기본 네트워크로, 동일한 호스트 내의 컨테이너 간 통신을 지원.
    • 호스트(host): 컨테이너가 호스트와 네트워크를 공유하도록 설정.
    • 오버레이(overlay): 여러 Docker 데몬 간 네트워크를 구성할 때 사용.

Docker Image와 Docker Container

  • Docker Image와 DOcker Container는 프로그램과 프로세스와 같은 관계이다. Docker file로 생성된 읽기 전용 파일 Image를 build하면, 해당 Image를 run 명령으로 실행시켜 Docker container를 구축한다.
  • 하나의 Image로 여러 개의 Container를 만들 수도 있다.

Docker 세팅

Dockerfile

  • 빌드 단계 (builder):
    • Node.js 이미지를 사용해 React 애플리케이션을 빌드.
    • npm install로 의존성을 설치하고 npm run build로 빌드 파일을 생성.
  • 서빙 단계 (nginx):
    • Nginx를 이용해 정적 파일(build/ 폴더)을 서빙.
    • COPY --from=builder를 통해 빌드 결과를 Nginx의 기본 경로로 복사.
# 1. Node.js 이미지로 애플리케이션 빌드
FROM node:18 AS builder

# 2. 작업 디렉토리 설정
WORKDIR /app

# 3. 패키지 파일 복사 및 의존성 설치
COPY package.json package-lock.json ./
RUN npm install

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

# 5. Nginx를 사용해 정적 파일 서빙
FROM nginx:alpine

# 6. Nginx 설정 파일 덮어쓰기 (옵션)
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 7. React 빌드 결과를 Nginx의 기본 경로로 복사
COPY --from=builder /app/dist /usr/share/nginx/html

# 8. 컨테이너 포트 설정
EXPOSE 80

# 9. Nginx 시작
CMD ["nginx", "-g", "daemon off;"]

nginx.conf

  • Nginx를 사용하여 React와 같은 SPA(Single Page Application)를 배포하는 데 일반적으로 사용되는 설정 파일
// server 블록 - 서버 설정을 정의합니다.
server {
		// Nginx가 요청을 수신할 포트를 지정
    listen 80; 
    // 이 서버 블록에서 처리할 요청의 도메인 지정
    server_name localhost;

		// 정적 파일(HTML, CSS, JS 등)이 저장된 루트 디렉터리를 설정
		// 일반적으로 Nginx 컨테이너에서 기본적으로 사용하는 디렉터리
    root /usr/share/nginx/html;
    index index.html;

		// 특정 URL 경로에 대해 별도의 동작을 정의.
    // 기본 경로를 의미하며, 모든 요청이 이 블록에 매칭
    location / {
        try_files $uri /index.html;
    }
}

docker-compose.yml

일반적으로 여러 개의 컨테이너를 한 번에 관리할 때 사용되지만, 하나의 컨테이너 올릴 때도 compose로 동작하면 편리하다.

version: '3.8'
services:
  react-app:
    build:
      context: .
    ports:
      - "3000:80"

동작 확인

Window - Docker Desktop 환경

컨테이너 상에서 nginx의 80 포트에 프로젝트의 build된 index.html이 잘 라우팅 되는 것을 확인할 수 있다.

3000포트와 연결해서 docker network 외부에서도 잘 동작하는 것을 확인할 수 있다.

VirtualBox - 우분투 환경

레포지토리를 클론해서 docker compose 명령을 실행하면 윈도우 상에서 컨테이너를 돌렸을 때와 동일한 동작을 하는 것을 확인할 수 있다.

.env 파일이 .gitignore에 포함되어 따로 넣어주어야 한다는 점, vite 환경에서는 localhost:5173, 5174으로 포트를 열었기에 kakao API에서 도메인에 3000번 포트로 열려있어야 한다는 점에 유의하자.

레퍼런스

‘Docker란 무엇인가’가 무엇인지 모르겠다

[Docker] 1. 도커란 무엇인가🤔?

profile
씨앗 개발자

0개의 댓글