728x90

아래처럼 빌드 과정에서만 node_modules가 필요하고, 최종 실행은 다른 산출물만으로 가능한 경우입니다.
- React / Vue / Next.js 일부 정적 빌드 결과물만 실행
- TypeScript를 JS로 컴파일한 뒤, 런타임에는 컴파일된 결과만 실행
- 번들러(Webpack, Vite, esbuild 등)로 의존성을 묶어서 배포
이 경우에는 보통 최종 이미지에 node_modules를 넣지 않거나, production dependency만 남기는 방식으로 줄입니다.
언제 제거하면 안 되나
아래 경우는 실행 시점에 node_modules가 필요합니다.
- Node.js 서버가
require()/import로 패키지를 직접 사용 - NestJS, Express, Fastify 같은 서버 앱
- ORM, DB 드라이버, 템플릿 엔진, 로그 라이브러리 등을 런타임에 쓰는 경우
이때 node_modules를 지우면 컨테이너는 올라가도 앱이 바로 에러 납니다.
즉, “빌드용 의존성”과 “실행용 의존성”을 분리해야 합니다.
가장 권장하는 방식: 멀티 스테이지 빌드
보통은 이렇게 합니다.
# 1) build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 2) runtime stage
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]
이 방식의 장점은 다음과 같습니다.
- 빌드용 툴체인과 소스코드가 최종 이미지에 남지 않음
- 최종 이미지가 작아짐
- 보안상 공격면이 줄어듦
- 캐시 효율이 좋아짐
node_modules를 그냥 지우는 것보다 더 좋은 방법
같은 stage에서 빌드 후 rm -rf node_modules를 하는 것도 가능하지만, 보통은 권장하지 않습니다.
- 빌드 후 런타임에 필요한 패키지까지 같이 지워질 수 있음
- Docker 레이어 특성상 “지웠다” 해도 중간 레이어에 흔적이 남음
- 최종 이미지 최적화 효과가 멀티 스테이지보다 떨어짐
즉, 삭제보다 분리가 더 좋습니다.
이미지 최적화에 같이 쓰면 좋은 방법
.dockerignore
불필요한 파일이 빌드 컨텍스트에 들어가지 않게 합니다.
node_modules
dist
.git
Dockerfile
npm-debug.log
npm ci --omit=dev
운영 이미지에는 devDependencies를 제외합니다.
npm ci --omit=dev
Alpine 또는 slim 이미지 사용
예
node:20-alpinenode:20-slim
300x250
다만 native module이 많으면 Alpine(musl)에서 추가 빌드 이슈가 생길 수 있습니다.
npm prune --omit=dev
이미 설치된 상태에서 devDependencies만 제거할 때 사용합니다.
npm prune --omit=dev
판단 기준을 한 줄로 정리하면
- 빌드 결과물만 실행하면 된다 →
node_modules를 최종 이미지에서 제거 가능 - Node 앱이 직접 실행되며 패키지를 import 한다 →
node_modules는 남겨야 함 - 가장 좋은 방법 → 멀티 스테이지 빌드 + production dependency만 설치
실무에서 많이 쓰는 예시
TypeScript 서버
- builder에서 전체 설치
npm run build- runner에서
npm ci --omit=dev dist만 복사
React/Vite 정적 웹
- builder에서
npm ci후 build - 최종 이미지는
nginx같은 정적 서버만 사용 node_modules는 최종 이미지에 불필요
728x90
그리드형(광고전용)
댓글