개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Multi-stage Build — 이미지 크기를 줄이는 빌드 최적화

2025년 7월 10일
DockerMulti-stage Build이미지 크기Production최적화

Docker 이미지를 배포할 때 컴파일러, 패키지 매니저, 소스 코드가 그대로 포함된 채 수백 MB 단위로 불어나는 경우가 흔하다. Multi-stage Build는 빌드 환경과 런타임 환경을 명시적으로 분리해, 최종 이미지에는 실행에 필요한 파일만 남긴다. 같은 Dockerfile 하나로 이 분리를 완전히 처리할 수 있다.

단일 스테이지의 문제

단일 스테이지 빌드에서는 빌드 도구가 최종 이미지에 그대로 잔류한다.

[ 단일 스테이지 ]

  node:20 (base)
  ├── npm install     ← node_modules 전체
  ├── tsc / webpack   ← 빌드 도구
  ├── src/            ← 소스 코드
  └── dist/           ← 빌드 결과물
                        ↓
                    최종 이미지 ~900 MB

공격 표면이 넓어지고, 레지스트리 전송 시간과 컨테이너 기동 시간도 늘어난다.

Multi-stage Build 구조

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 배포판. 빌드 도구가 없어 런타임 전용으로 적합하다.

실제 Dockerfile 예시 (Node.js + TypeScript)

# ── 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만
최종 이미지 포함 여부XO

개발·빌드·런타임 분리 확장

스테이지는 두 개에 국한되지 않는다. test 스테이지를 중간에 두어 CI에서 --target test로 테스트만 실행하고, 통과 시 runtime 스테이지를 빌드하는 패턴도 일반적이다.

# 특정 스테이지까지만 빌드
docker build --target builder -t myapp:builder .

# 최종 런타임 이미지 빌드
docker build -t myapp:latest .

--target — 지정한 스테이지에서 빌드를 멈춘다. 이후 스테이지는 실행되지 않으므로 CI 단계별 캐시 활용에 유용하다.

빌드 캐시 레이어 순서도 중요하다. 변경 빈도가 낮은 package*.json 복사와 npm ci를 소스 코드 복사보다 앞에 두면, 의존성 레이어가 캐시되어 재빌드 시간이 단축된다.

← 이전 글Docker Logging — 컨테이너 로그를 수집하는 방법
다음 글 →Docker 보안 — 컨테이너 보안 체크리스트