Server || Infra/Docker

[Docker/error handling] React, 격리환경 구성과 핫 리로딩(hot reloding)

기록하는 습관. 2025. 1. 18. 05:06

팀에서 Docker를 사용하면 운영체제와 상관없이 일관성있는 개발환경을 유지할 수 있습니다.
이번 포스팅에서는 로컬 환경 React코드가 실시간으로 반영(핫리로드)되는 Docker 격리환경(빌드환경) 구성을 목표로 합니다.


STEP 1. Dockerfile

Docker는 Dockerfile을 기반으로 이미지를 생성하고, 이 이미지를 Docker 컨테이너로 실행하는 흐름을 갖습니다.** 하나의 도커 이미지로 여러 개의 도커 컨테이너를 만들 수 있고, 각각의 컨테이너는 독립적으로 실행됩니다.


  • Dockerfile
FROM node:18

# Set working directory
WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm install

# Copy all files
COPY . .

# Expose port
EXPOSE 3000

# Start the application
CMD ["npm", "start"]

첫번째로 저는 CRA로 React프로젝트를 생성하고 프로젝트 루트에 Dockerfile을 작성해주었습니다.


FROM은 베이스 이미지를 지정합니다. 베이스 이미지란 애플리케이션 실행 환경을 제공합니다.
WROKDIR는 컨테이너 내에서 작업 디렉토리를 설정하는 명령어입니다. 즉, 파일들이 저장될 위치를 지정합니다.
COPY는 호스트 컴퓨터에 있는 파일을 컨테이너 작업 디렉터리로 복사하는 명령어입니다.
RUN npm install은 컨테이너에 의존성 라이브러리를 설치하는 명령어입니다.
EXPOSE는 컨테이너 내부에서 특정 포트를 열어주는 명령어로 설정 포트를 통해 요청을 수신합니다.
CMD는 컨테이너 실행될 때 실행할 명령을 지정합니다.



STEP 2. docker-compose.yml

Docker compose는 아래와 같은 이유로 필요합니다.

  • 여러개의 Docker 컨테이너를 정의하고 관리
    • 예를 들어 백엔드, 프론트엔드, 데이터베이스의 각각의 컨테이너가 필요할 수 있는데 Docker Compose 도구를 이용하면 한 번에 실행하고 관리할 수 있습니다.
  • 각각의 컨테이너 간 네트워킹
    • 각각의 컨테이너들은 Docker compose가 자동으로 네트워크를 설정하여 통신할 수 있도록 해줍니다.
  • 간단한 설정
    • 복잡한 명령어 없이 하나의 설정 파일에 모든 구성을 작성하고 실행할 수 있습니다.
  • 환경변수 관리
    • .env파일을 사용해 환경 변수도 쉽게 관리할 수 있습니다.

이번 Docker compse를 작성하는 목적은 핫 리로딩(hot reloding)을 사용하는데 의의를 둡니다.
핫 리로딩이란, 로컬 환경의 소스 코드가 변경되었을 때 이를 컨테이너가 실시간으로 반영하는 것을 의미합니다.


  • docker-compose.yml
# 서비스로 실행하려는 컨테이너를 정의
services: 
  # 나의 프로젝트 이름, 보통은 app으로 적어주지만 
  # 여러 컨테이너를 사용할 수 있는 여지가 있어 식별하기 위해 식별할 수 있는 고유 이름으로 지정
  dev-hiresetup-web:
    # 빌드할 Dockerfile 관련 설정
    build:
      context: . # Dockerfile의 위치
      dockerfile: Dockerfile # 찾을 Dockerfile의 이름
    # Dockerfile 빌드 대상 이미지 이름 설정
    image: hiresetup-web:latest
    # 마운트 설정 
    volumes:
      - .:/dev-hiresetup-web 
      #⬆️호스트 바인드 마운트
      # 호스트의 현재 디렉토리를 컨테이너의 /hiresetup-web으로 마운트함을 의미함
      # 이는 컨테이너 내부의 /hiresetup-web 디렉토리에 호스트의 모든 파일을 덮어                         # 씌우게 됌
      - /dev-hiresetup-web/node_modules 
      # ⬆️익명 볼륨 (볼륨이란, 저장소로 이해해도 좋다)
      # 컨테이너 내부 /dev-hiresetup-web/node_modules 경로의 파일을 익명의 볼륨으로 관리한다.
      # 익명의 볼륨은 호스트 파일과 상관없이 Docker가 독립적으로 관리한다. 
      # 설정의 필요성은 컨테이너 내부에서만 필요한 파일을 독립적으로 관리할 때 사용한다. 
      # 사실 개발환경에서는 호스트 바인드 마운트로 사용하는 것이 효율적이다.
      # 만약, 해당 설정을 하고 오류없이 사용하려면 컨테이너에 접속하여 npm install을 해주어야한다.
      # 명심하자. 익명 볼륨과 호스트의 해당 파일은 분리되어 있고 설정시 컨테이너는 익명볼륨을 바라본다.

    ports:
      - "3000:3000" # 호스트 3000번 포트로 컨테이너 3000번 포트에 접속한다.
    environment:
      - WATCHPACK_POLLING=true # window에서 핫 리로딩(hot reloding)을 동작시키기 위해 설정한다.
    command: [ "npm", "start" ] # 컨테이너 실행 후, 명령어를 실행한다.

설명은 주석으로 대체합니다.

해당 설정이 완료되었으면 docker-compose up --build로 Docker Compose를 실행합니다.



수정사항 (error handling)

[1] 2025-01-19. Dockerfile => 핫 로딩이 되지 않는 문제

[원인] Dockerfile의 WORKDIR과 Docker Compose 마운트 볼륨의 불일치

FROM node:18

# Set working directory
WORKDIR /dev-hiresetup-web-app

# Install dependencies
COPY package*.json ./
RUN npm install

# Copy all files
COPY . .

# Expose port
EXPOSE 3000

# Start the application
CMD ["npm", "start"]

기존 Dockerfile입니다. 여기서 WORKDIR /app을 WORKDIR /dev-hiresetup-web-app로 바꿔주었습니다.
바꾼 이유는, 컨테이너 내부에서 /app 디렉토리를 바라보는 것이 아니라, /dev-hiresetup-web-app을 바라보게 설정합니다. 이를 바꿔주지 않으면 핫리로딩이 동작하지 않습니다.


[2] 2025-01-20. Dockerfile => Typescript와 react-scripts의 호환성 문제 해결

Image

프로젝트에 tsx 파일을 만들고 docker-compose up --build를 하니 문제가 발생했습니다.
문제의 핵심은 react-scripts@5.0.1typescirpt@5.7.3사이에서 호환성 문제가 있다는 것이었습니다.


# Base image 설정
FROM node:22

# 작업 디렉토리 설정
WORKDIR /dev-hiresetup-web-app

# package.json 복사
COPY package*.json ./

# TypeScript 다운그레이드 및 의존성 설치
RUN npm uninstall --save-dev typescript && \
  npm install --save-dev typescript@4.9.5 && \
  npm install --legacy-peer-deps

# 소스 코드 복사
COPY . .

# 앱 실행
CMD ["npm", "start"]

react-scripts@5.0.1typescript 버전으로 3.2.1 또는 4를 요구합니다.
호환성을 맞춰주기 위해 주석 부분인 # TypeScript 다운그레이드 및 의존성 설치를 추가해줬습니다.


[추가]

  1. npm uninstall --save-dev typescript
    npm install --save-dev typescript@4.9.5
    npm install --legacy-peer-deps

docker의 설정은 Dockerfile의 내용으로 충분합니다.
만약 로컬에서 프로젝트 환경을 맞추길 원한다면 해당 명령어를 CMD에 입력해줍니다.


  1. # tsconfig.json
    {
    "compilerOptions": {
     "target": "ES6",                                // 코드가 변환될 ECMAScript 버전
     "lib": ["DOM", "DOM.Iterable", "ES6"],         // 사용할 라이브러리
     "module": "ESNext",                            // 모듈 시스템
     "jsx": "react-jsx",                            // React JSX 지원
     "strict": true,                                // 엄격한 타입 검사
     "esModuleInterop": true,                       // ES 모듈 및 CommonJS 간의 호환성
     "skipLibCheck": true,                          // 라이브러리 타입 검사 건너뛰기
     "forceConsistentCasingInFileNames": false,      // 파일 이름 대소문자 일관성 강제
     "allowJs": true,                               // JS 파일 허용
     "checkJs": false,                              // JS 파일 타입 검사 비활성화
     "allowImportingTsExtensions": true,            // .tsx 확장 포함한 import 허용
     "resolveJsonModule": true,                     // JSON 파일 가져오기 허용
     "isolatedModules": true,                       // 개별 모듈로 트랜스파일링
     "noEmit": true,                                // 트랜스파일된 파일 생성 방지
     "moduleResolution": "Node",                   // Node.js 모듈 해석 방식
     "baseUrl": ".",                                // 경로 기본값
     "paths": {                                     // 경로 별칭 설정
       "@components/*": ["src/components/*"],
       "@utils/*": ["src/utils/*"]
     }
    },
    "include": ["src"],                              // 포함할 파일 및 폴더
    "exclude": ["node_modules", "build"]             // 제외할 파일 및 폴더
    }

Typescript 설정 파일입니다. 설정파일은 프로젝트 루트에 위치해야합니다.
해당 파일의 필요성은 처음 타입스크립트를 적용하고 코드 파일을 보면 빨간 줄이 나오는 경우가 있습니다.
이를 해결해주기 위해 CHAT GPT를 이용해 얻은 설정파일입니다.