도커 이미지 다이어트: 멀티 스테이지 빌드(Multi-stage build)로 90% 감량하기

1. 왜 '뚱뚱한' 도커 이미지는 나쁜가?

컨테이너 기술의 핵심은 가볍고 빠른 배포입니다. 하지만 많은 개발자들이 애플리케이션을 빌드하는 과정에서 불필요한 파일들을 최종 이미지에 그대로 포함시키는 실수를 저지릅니다. 예를 들어, Java 애플리케이션을 빌드하기 위해 JDK, Maven, Gradle과 같은 빌드 도구와 소스 코드, 중간 빌드 결과물(.class 파일 등)이 모두 최종 이미지에 포함된다면 어떻게 될까요?

이렇게 만들어진 '뚱뚱한(Fat)' 이미지는 여러 가지 문제를 야기합니다.

  • 느린 CI/CD 파이프라인: 이미지 크기가 크면 빌드, 푸시, 풀(pull)하는 데 더 많은 시간이 걸려 배포 속도가 저하됩니다.
  • 저장 비용 증가: 이미지 레지스트리(ECR, Docker Hub 등)에 더 많은 저장 공간을 차지하여 비용이 증가합니다.
  • 보안 취약점 증가: 실제 서비스 실행에 필요 없는 빌드 도구나 라이브러리가 포함되면, 그만큼 공격에 노출될 수 있는 잠재적인 보안 취약점(Attack Surface)도 넓어집니다.

2. 해법은 '빌드'와 '실행'의 분리: 멀티 스테이지 빌드

이러한 문제를 해결하기 위한 가장 효과적인 기법이 바로 멀티 스테이지 빌드(Multi-stage build)입니다. 멀티 스테이지 빌드는 하나의 Dockerfile 안에서 여러 개의 빌드 단계를 정의하는 기능입니다. 각 단계는 독립적인 FROM 명령어로 시작하며, 최종적으로는 마지막 단계의 결과물만 이미지로 만들어집니다.

핵심 아이디어는 애플리케이션을 컴파일하고 빌드하는 '빌드 환경'과, 빌드된 결과물을 실행만 시키는 '실행 환경'을 완전히 분리하는 것입니다. 빌드 단계에서는 필요한 모든 도구(컴파일러, 빌드 툴 등)를 자유롭게 사용하고, 최종 실행 단계에서는 빌드 단계에서 생성된 실행 파일(예: Go 바이너리, Java의 .jar 파일)만 쏙 가져와 아주 가벼운 베이스 이미지 위에 올려놓는 방식입니다.

Go 애플리케이션 예제

간단한 Go 웹서버를 멀티 스테이지 빌드로 패키징하는 예시입니다.

# ===== 1단계: 빌드(Build) 스테이지 =====
# Go 컴파일러가 포함된 이미지를 'builder'라는 이름으로 사용
FROM golang:1.19-alpine AS builder

# 작업 디렉토리 설정
WORKDIR /app

# Go 모듈 의존성 다운로드
COPY go.mod ./
COPY go.sum ./
RUN go mod download

# 소스 코드 복사 및 애플리케이션 빌드
COPY *.go ./
RUN go build -o /app/main .


# ===== 2단계: 실행(Runtime) 스테이지 =====
# 실제 서비스 실행에는 Go가 필요 없으므로, 가장 가벼운 alpine 이미지를 사용
FROM alpine:latest

# 작업 디렉토리 설정
WORKDIR /app

# 'builder' 스테이지에서 빌드된 실행 파일만 복사해옴!
COPY --from=builder /app/main .

# 컨테이너 실행 시 실행될 명령어
CMD ["/app/main"]

위 예제에서 최종적으로 만들어지는 이미지는 2단계(실행 스테이지)의 결과물입니다. 이 이미지에는 Go 컴파일러나 소스 코드는 전혀 포함되지 않고, 오직 alpine 베이스 이미지와 main이라는 실행 파일 단 하나만 존재하게 됩니다. 그 결과, 이미지 크기는 수백 MB에서 수십 MB 수준으로 획기적으로 줄어듭니다.

3. 한눈에 비교: 싱글 스테이지 vs. 멀티 스테이지

구분 싱글 스테이지 빌드 멀티 스테이지 빌드
Dockerfile 구조 하나의 FROM으로 시작하여 모든 과정을 처리 여러 개의 FROM을 사용하여 빌드와 실행 단계를 분리
최종 이미지 크기 매우 큼 (수백 MB ~ GB 단위) 매우 작음 (수십 MB 단위)
포함된 내용물 실행 파일 + 소스 코드, 컴파일러, 빌드 도구 등 오직 실행 파일과 최소한의 런타임 라이브러리
보안 공격 표면이 넓어 상대적으로 취약 불필요한 구성요소가 없어 훨씬 안전
CI/CD 성능 이미지 푸시/풀 시간이 오래 걸림 매우 빠르고 효율적

도커 이미지 경량화는 선택이 아닌 필수적인 최적화 과정입니다. 멀티 스테이지 빌드는 복잡한 스크립트나 별도의 도구 없이 Dockerfile 하나만으로 더 작고, 더 빠르며, 더 안전한 컨테이너 이미지를 만들 수 있는 가장 강력하고 표준적인 방법입니다. 프로덕션 환경을 위한 컨테이너를 만든다면, 멀티 스테이지 빌드를 적용하는 것은 이제 기본 소양이라고 할 수 있습니다.

댓글