blog

Vite 프론트엔드 앱을 Docker로 배포하기와 자주 겪는 에러

Front-end / DevOps#Docker#Docker Compose#Vite#nginx

1. Dockerfile

정적 SPA는 빌드 결과물만 웹 서버로 제공하면 된다. 개발 서버(npm run dev)를 컨테이너에서 띄우는 방식은 운영 배포에 쓰지 않는다. 빌드 단계와 실행 단계를 나눈 멀티 스테이지 빌드를 사용한다.

# 1단계: 빌드
FROM node:22-alpine AS build
WORKDIR /app

# 의존성 파일만 먼저 복사해 레이어 캐시를 활용
COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

# 2단계: 정적 파일 제공
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

node:latest처럼 태그를 고정하지 않으면 빌드할 때마다 Node 메이저 버전이 바뀔 수 있으니 버전을 명시한다. SPA 라우팅을 쓴다면 nginx 설정에 try_files $uri /index.html;을 추가해야 새로고침 시 404가 나지 않는다.

.dockerignore도 함께 둔다. 호스트의 node_modules가 이미지로 복사되는 것을 막는 것이 핵심이다(아래 에러 참고).

# .dockerignore
node_modules
dist
.git
.env*

2. docker-compose.yml

services:
  web:
    image: my-app:1.0.0
    build: .
    restart: always
    ports:
      # 호스트 포트:컨테이너 포트(EXPOSE한 포트)
      - "8080:80"

ports는 호스트:컨테이너 순서다. 컨테이너 안의 nginx가 80에서 동작하므로 오른쪽은 80이어야 한다. 최신 Docker Compose에서는 맨 위의 version: 키가 더 이상 필요 없다.

3. 빌드와 실행

# 이미지 빌드 (이름:태그 지정)
docker build -t my-app:1.0.0 .

# 컨테이너 실행: 호스트 8080 → 컨테이너 80
docker run -d -p 8080:80 --name my-app my-app:1.0.0

Compose를 쓴다면:

docker compose up -d --build

docker compose build는 이미지만 만들고 컨테이너를 띄우지 않는다. 실행까지 하려면 up을 사용한다. 이후 http://localhost:8080으로 접속한다.

4. 이미지를 파일로 옮기기 (save / load)

레지스트리 없이 서버로 이미지를 옮길 때 사용한다.

# 로컬: 이미지를 tar로 저장
docker save -o my-app.tar my-app:1.0.0

# 서버: tar에서 이미지 불러오기
docker load -i my-app.tar

Apple Silicon Mac에서 빌드한 이미지를 x86 서버에서 실행하려면 docker build --platform linux/amd64 ...로 빌드해야 한다.

5. 자주 쓰는 명령어

명령어설명
docker ps실행 중인 컨테이너 목록
docker ps -a중지된 컨테이너 포함 전체 목록
docker images로컬 이미지 목록
docker logs -f <컨테이너>컨테이너 로그 확인
docker stop <컨테이너>컨테이너 중지
docker rm <컨테이너>중지된 컨테이너 삭제
docker rmi <이미지>이미지 삭제
docker system prune -a사용하지 않는 컨테이너·네트워크·이미지·빌드 캐시 삭제 (볼륨은 --volumes를 붙여야 삭제됨)

6. 자주 겪는 에러

Cannot find module @rollup/rollup-linux-x64-gnu

You installed esbuild for another platform than the one you're currently using

두 에러는 원인이 같다. Rollup과 esbuild는 플랫폼별 네이티브 바이너리를 설치하는데, macOS·Windows에서 설치한 node_modules가 COPY . .으로 리눅스 컨테이너에 그대로 들어가면 플랫폼이 맞지 않는다.

@rollup/rollup-linux-x64-gnu를 직접 설치하거나 esbuild-wasm으로 바꾸는 우회책도 있지만, 근본 해결은 다음 두 가지다.

  1. .dockerignore에 node_modules를 넣어 호스트 의존성이 이미지로 들어가지 않게 한다.
  2. 컨테이너 안에서 npm ci로 의존성을 새로 설치한다.

lockfile을 macOS에서 만들었는데 리눅스용 optional 의존성이 빠져 같은 에러가 나는 경우도 있다(npm 이슈 #4828). 이때는 package-lock.json과 node_modules를 지우고 다시 설치한 lockfile을 커밋한다.