Docker 이미지를 배포할 때 컴파일러, 패키지 매니저, 소스 코드가 그대로 포함된 채 수백 MB 단위로 불어나는 경우가 흔하다. Multi-stage Build는 빌드 환경과 런타임 환경을 명시적으로 분리해, 최종 이미지에는 실행에 필요한 파일만 남긴다. 같은 Dockerfile 하나로 이 분리를 완전히 처리할 수 있다.
단일 스테이지 빌드에서는 빌드 도구가 최종 이미지에 그대로 잔류한다.
[ 단일 스테이지 ]
node:20 (base)
├── npm install ← node_modules 전체
├── tsc / webpack ← 빌드 도구
├── src/ ← 소스 코드
└── dist/ ← 빌드 결과물
↓
최종 이미지 ~900 MB
공격 표면이 넓어지고, 레지스트리 전송 시간과 컨테이너 기동 시간도 늘어난다.
FROM ... AS <name> 구문으로 스테이지에 이름을 붙이고, COPY --from=<name>으로 이전 스테이지의 결과물만 선택적으로 가져온다.
[ Multi-stage 흐름 ]
Stage 1: builder
┌─────────────────────────┐
│ node:20 │
│ COPY package*.json │
│ RUN npm ci │
│ COPY src/ │
│ RUN npm run build │
└────────┬────────────────┘
│ COPY --from=builder /app/dist
▼
Stage 2: runtime
┌─────────────────────────┐
│ node:20-alpine │
│ dist/ ← 결과물만 │
│ node_modules (prod only)│
└─────────────────────────┘
↓
최종 이미지 ~180 MB
alpine — 기본 이미지 대비 약 5배 작은 경량 Linux 배포판. 빌드 도구가 없어 런타임 전용으로 적합하다.
# ── Stage 1: build ──────────────────────────────
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
# ── Stage 2: runtime ────────────────────────────
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
COPY --from=builder /app/dist ./dist 한 줄이 핵심이다. builder 스테이지의 파일시스템 전체가 아닌 dist/ 디렉터리만 복사되므로, 컴파일러·소스 코드·devDependencies가 최종 이미지에 포함되지 않는다.
| 항목 | builder 스테이지 | runtime 스테이지 |
|---|---|---|
| 베이스 이미지 | node:20 (~1 GB) | node:20-alpine (~180 MB) |
| 포함 내용 | 소스, devDeps, 빌드 도구 | dist/, prodDeps만 |
| 최종 이미지 포함 여부 | X | O |
스테이지는 두 개에 국한되지 않는다. test 스테이지를 중간에 두어 CI에서 --target test로 테스트만 실행하고, 통과 시 runtime 스테이지를 빌드하는 패턴도 일반적이다.
# 특정 스테이지까지만 빌드
docker build --target builder -t myapp:builder .
# 최종 런타임 이미지 빌드
docker build -t myapp:latest .
--target — 지정한 스테이지에서 빌드를 멈춘다. 이후 스테이지는 실행되지 않으므로 CI 단계별 캐시 활용에 유용하다.
빌드 캐시 레이어 순서도 중요하다. 변경 빈도가 낮은 package*.json 복사와 npm ci를 소스 코드 복사보다 앞에 두면, 의존성 레이어가 캐시되어 재빌드 시간이 단축된다.