← 아티클 목록

도커 멀티스테이지 빌드 원리 이해하기 — 가벼운 이미지

2028-10-30#docker#멀티스테이지#빌드

Go나 자바, 프런트엔드 앱을 도커로 빌드하면 이미지가 수백 MB로 불어나는 경우가 많습니다. 컴파일러, 빌드 도구, 의존성 캐시까지 전부 이미지에 남기 때문입니다. **멀티스테이지 빌드(multi-stage build)**는 "빌드하는 단계"와 "실행하는 단계"를 분리해, 최종 이미지에는 실행에 꼭 필요한 결과물만 남기는 기법입니다.

왜 단계를 나누는가

하나의 Dockerfile 안에 FROM을 여러 번 써서 여러 스테이지를 만들 수 있습니다. 핵심은 마지막 스테이지가 곧 최종 이미지가 되고, 앞 스테이지들은 결과물을 꺼내 쓰고 나면 버려진다는 점입니다.

단계베이스 이미지포함되는 것
빌드 스테이지golang, node 등(무거움)컴파일러, 소스, 의존성
실행 스테이지alpine, distroless(가벼움)컴파일된 바이너리뿐

빌드 도구는 빌드할 때만 필요하지 실행할 때는 짐일 뿐입니다. 멀티스테이지는 이 짐을 최종 이미지에서 떼어냅니다.

COPY --from 으로 결과만 꺼내기

핵심 문법은 COPY --from=<스테이지>입니다. 앞 스테이지에서 만든 파일을 다음 스테이지로 가져옵니다.

Dockerfile
# 1단계: 빌드 (이 스테이지는 최종 이미지에 안 남음)
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o /app/server ./cmd/server

# 2단계: 실행 (이게 최종 이미지)
FROM gcr.io/distroless/static
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

최종 이미지에는 golang 베이스도, 소스 코드도, 빌드 캐시도 없습니다. 오직 /server 바이너리 하나만 distroless 위에 얹힙니다. 결과적으로 수백 MB짜리가 수십 MB 이하로 줄어듭니다.

용량 말고도 얻는 것

작아지는 것만이 이득이 아닙니다. 빌드 도구와 셸, 패키지 매니저가 빠지면 **공격면(attack surface)**도 함께 줄어듭니다. 컨테이너 안에 컴파일러가 없으니 침투해도 할 수 있는 일이 적습니다.

빌드 결과는 docker history나 이미지 크기로 바로 확인할 수 있습니다.

Docker
docker build -t my-app .
docker images my-app
docker history my-app

자주 하는 실수

특정 스테이지까지만 빌드하고 싶다면 --target을 씁니다. 디버깅 시 빌드 스테이지만 떼어 볼 때 유용합니다.

Docker
docker build --target builder -t my-app-build .

또 흔한 실수는 COPY --from의 경로를 빌드 스테이지의 실제 산출 경로와 안 맞추는 것입니다. 앞 스테이지에서 파일이 어디 생기는지 먼저 확인하세요.

요점 정리

  • 멀티스테이지는 빌드 단계와 실행 단계를 분리해 최종 이미지를 가볍게 한다.
  • COPY --from으로 앞 스테이지의 결과물만 꺼내 온다.
  • 빌드 도구가 빠지면 용량뿐 아니라 보안 공격면도 줄어든다.
  • --target으로 특정 스테이지까지만 빌드할 수 있다.

무거운 이미지를 멀티스테이지로 직접 줄여 보며 용량 차이를 확인하는 실습은 도커 트랙에서 할 수 있습니다 — 회원가입 없이 무료로.