Docker Builder Pattern

빌더 패턴은 애플리케이션을 빌드하는 환경과 실행하는 환경을 분리하여 최종 Docker 이미지의 크기를 줄이고 보안을 강화하는 패턴입니다.

이를 가능하게 하는 핵심 기술이 바로 **멀티 스테이지 빌드(Multi-stage builds)**입니다.


1. 왜 빌더 패턴을 사용해야 할까요?

기존의 방식(Single-stage build)으로 Docker 이미지를 만들 때 발생하는 문제점들을 해결하기 위해서입니다.

  • 😭 불필요하게 거대한 이미지 크기:
    • 애플리케이션을 빌드하기 위해서는 컴파일러, SDK, 라이브러리 등(e.g., JDK, Maven, Go toolchain, node_modules)이 필요합니다.
    • 기존 방식에서는 이 모든 빌드 도구와 소스 코드까지 최종 이미지에 포함되어, 실제 실행에는 필요 없는 파일들 때문에 이미지 크기가 매우 커졌습니다.
  • 😨 보안 취약점 증가:
    • 이미지에 포함된 패키지와 도구가 많을수록 잠재적인 보안 공격의 표면(Attack Surface)이 넓어집니다.
    • 런타임에 전혀 필요 없는 빌드 도구의 취약점이 해킹의 경로가 될 수 있습니다.
  • 🤔 빌드와 런타임의 불분명한 분리:
    • 하나의 Dockerfile에서 모든 작업을 처리하다 보니, 어디까지가 빌드를 위한 과정이고 어디부터가 실행을 위한 구성인지 파악하기 어렵습니다.

빌더 패턴은 이러한 문제들을 아주 효과적으로 해결합니다.


2. 빌더 패턴의 동작 원리: 멀티 스테이지 빌드 (Multi-stage Builds)

멀티 스테이지 빌드는 하나의 Dockerfile 안에 여러 개의 FROM 구문을 사용하여 여러 빌드 단계를 정의하는 기능입니다.

  1. 빌드 스테이지 (Builder Stage): 첫 번째 단계에서는 애플리케이션 빌드에 필요한 모든 도구와 의존성을 갖춘 이미지(e.g., golang:1.22, maven:3.8-jdk-11)를 기반으로 소스 코드를 컴파일하거나 빌드하여 실행 가능한 결과물(e.g., Go 바이너리, Java .jar 파일, React 정적 파일)을 만듭니다.
  2. 런타임 스테이지 (Runtime Stage): 두 번째 단계에서는 애플리케이션 실행에 꼭 필요한 최소한의 환경을 갖춘 경량 이미지(e.g., alpine:latest, scratch, ubuntu-slim)를 기반으로 새로운 이미지를 만듭니다. 그리고 가장 중요한 부분으로, 이전 빌드 스테이지에서 생성된 결과물만 COPY --from 명령을 사용해 복사해옵니다.

결과적으로 최종 이미지는 빌드 도구나 소스 코드 없이, 오직 실행에 필요한 최소한의 파일과 결과물만 포함하게 됩니다.


# Dockerfile

# --- 1. 빌드 스테이지 (Builder Stage) ---
# golang 이미지를 'builder'라는 이름의 스테이지로 지정
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY main.go .

# Go 애플리케이션을 정적 바이너리로 빌드
# CGO_ENABLED=0: C 라이브러리 의존성 없이 순수 Go 코드로 빌드
# -ldflags="-w -s": 디버깅 심볼 제거하여 파일 크기 최적화
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o /app/my-app .


# --- 2. 런타임 스테이지 (Runtime Stage) ---
# 아무것도 없는 가장 가벼운 scratch 이미지를 기반으로 최종 이미지 생성
# (alpine 같이 최소한의 쉘 환경이 필요하다면 FROM alpine:latest 사용)
FROM scratch

WORKDIR /

# builder 스테이지에서 빌드된 결과물만 복사!
COPY --from=builder /app/my-app /my-app

# 8080 포트 노출
EXPOSE 8080

# 컨테이너 실행 시 애플리케이션 실행
CMD [ "/my-app" ]

 

 

언제, 어떻게 사용하면 좋을까요?

  • 컴파일이 필요한 언어: Go, Java, C++, Rust 등 컴파일 과정이 필요한 모든 언어에 필수적으로 사용됩니다.
  • 프론트엔드 애플리케이션: Node.js 환경에서 npm run build를 실행하여 정적인 HTML/CSS/JS 파일을 생성하고, Nginx와 같은 웹서버 이미지를 런타임 스테이지로 사용하여 빌드 결과물만 서빙할 때 매우 유용합니다.
  • 의존성 설치가 복잡한 경우: Python이나 Ruby처럼 빌드 시점에 필요한 라이브러리와 런타임에 필요한 라이브러리가 다른 경우에도 효과적으로 사용할 수 있습니다.

결론

**도커 빌더 패턴(멀티 스테이지 빌드)**은 더 이상 선택이 아닌 필수입니다. 이 패턴을 사용하면 다음과 같은 명확한 이점을 얻을 수 있습니다.

  • ✅ 극적인 이미지 크기 감소
  • ✅ 보안 강화
  • ✅ 더 빠른 이미지 배포 및 스케일링
  • ✅ 깔끔하고 유지보수하기 쉬운 Dockerfile
반응형

'인프라' 카테고리의 다른 글

CI/CD 파이프라인 설계: 도커, 젠킨스, 스프링 부트  (2) 2025.06.30
k8s란  (1) 2025.06.30
CICD pipeline을 위한 인프라지식  (1) 2025.06.25