Docker Multi-stage Build 이해와 활용
· 4 min read
도커 이미지를 만들다 보면 빌드 환경과 실행 환경을 분리하고 싶어질 때가 있습니다.
이때 유용한 기능이 바로 Multi-stage Build입니다. 이 포스트에서는 Multi-stage의 개념부터 실전 예시, 장점까지 살펴봅니다.
왜 Multi-stage Build가 필요한가?
기존 Dockerfile에서는 앱을 빌드하기 위한 종속성과 실행을 위한 환경이 한 이미지 안에 포함되곤 했습니다. 이로 인해:
- 이미지 크기가 커지고
- 보안 취약점 노출 범위가 넓어지며
- 실행에 불필요한 파일이 함께 배포되는 문제가 발생합니다.
Multi-stage Build는 이 문제를 해결하기 위해 등장한 기능입니다.
하나의 Dockerfile 안에서 여러 개의 이미지 단계를 정의하고, 필요한 산출물만 추출하여 최종 이미지에 포함시킬 수 있습니다.
기본 예시: Node.js + TypeScript 프로젝트
# 1단계: 빌드 환경
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 2단계: 실행 환경
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
CMD ["node", "dist/index.js"]
설명
| 구분 | 내용 |
|---|---|
AS builder | 빌드 단계를 별칭으로 지정 |
COPY --from=builder | 빌드 단계에서 산출물만 가져옴 |
npm ci --omit=dev | 실행에 불필요한 devDependencies 제거 |
장점
-
✅ 이미지 크기 감소
빌드 도구, 테스트 도구 등은 최종 이미지에 포함되지 않음 -
✅ 보안성 향상
실행 환경을 최소화함으로써 공격 표면 축소 -
✅ 빌드 속도 개선
캐시 활용이 더 효율적
고급 예시: Go 언어 프로젝트
FROM golang:1.20 AS builder
WORKDIR /src
COPY . .
RUN go build -o app .
FROM alpine
COPY --from=builder /src/app /app
ENTRYPOINT ["/app"]
Go처럼 바이너리만 추출 가능한 언어는 더욱 극단적으로 이미지 크기를 줄일 수 있습니다 (수 MB 수준).
실전 팁
- 중간 단계 캐시 활용을 위해
--target옵션을 활용할 수 있습니다. - CI/CD에서는 중간 빌드까지 테스트한 후 마지막 단계로 넘어가는 구조도 추천됩니다.
COPY시 불필요한 파일이 포함되지 않도록.dockerignore설정도 병행하세요.