Skip to main content

Docker Multi-stage Build 이해와 활용

· 4 min read
hwayoung kim
소주같은 개발자

도커 이미지를 만들다 보면 빌드 환경실행 환경을 분리하고 싶어질 때가 있습니다.
이때 유용한 기능이 바로 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 설정도 병행하세요.

Reference

Reference