멀티 스테이지 빌드란?
멀티 스테이지 빌드는 Docker 17.05 버전에서 도입된 기능으로, 하나의 Dockerfile 내에서 여러 빌드 단계를 정의할 수 있게 해주는 기술이다. 이 기술을 사용하면 빌드 환경과 실행 환경을 분리하여 최종 이미지 크기를 대폭 줄이고 보안을 강화할 수 있다.
기존 Docker 빌드의 문제점
전통적인 Docker 빌드 방식은 단일 FROM 명령어로 시작하는 하나의 이미지를 사용한다. FastAPI 애플리케이션을 배포하는 상황을 가정했을 때, 단일 단계 Dockerfile의 예시를 살펴보자:
FROM python:3.13
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
이 방식에서는 여러 문제가 발생한다:
- 빌드 도구가 최종 이미지에 포함됨: pip install 명령을 실행할 때, 많은 파이썬 패키지(특히 numpy, pandas, psycopg2 등)은 C/C++ 확장을 컴파일해야 한다. 이 과정에서 gcc, g++, 개발 헤더 파일 등의 빌드 도구가 필요하며, 이들이 최종 이미지에 그대로 남게 된다.
- 중간 빌드 파일이 이미지에 포함됨: 패키지 설치 과정에서 생성된 소스 코드, 컴파일된 객체 파일(.o), 임시 빌드 디렉토리 등이 이미지 레이어에 그대로 남아 불필요하게 이미지 크기를 키운다.
- 보안 취약점 증가: 추가된 도구와 라이브러리는 잠재적인 공격 표면을 넓히고, 보안 취약점에 노출될 가능성을 높인다.
- 배포 및 확장 시 네트워크 부하 증가: 큰 이미지는 컨테이너 레지스트리에서 다운로드하고 노드 간에 전송하는 데 더 많은 시간이 소요된다.
멀티 스테이지 빌드의 작동 원리
멀티 스테이지 빌드의 핵심은 여러 FROM 명령어를 사용하여 서로 다른 기본 이미지에서 시작하는 여러 빌드 단계를 정의하는 것이다. 각 단계는 고유한 목적을 가지며, 이전 단계에서 생성된 특정 파일이나 결과물만 다음 단계로 복사된다.
멀티 스테이지 빌드를 사용하면:
- 빌드 단계에서만 빌드 도구를 사용: 컴파일러, 개발 헤더, 빌드 도구 등은 첫 번째 단계에서만 사용되고 최종 이미지에는 포함되지 않는다.
- 중간 빌드 파일 제외: 패키지 설치 과정에서 생성된 임시 파일과 빌드 아티팩트는 최종 이미지에 포함되지 않는다.
- 필요한 결과물만 복사: 빌드 단계에서 생성된 실행 파일이나 컴파일된 바이너리(wheel 등)만 최종 이미지로 복사된다.
아래 예시 Dockerfile을 통해 멀티 스테이지 빌드의 구조를 살펴보자:
# 빌드 단계
FROM python:3.13 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /app/wheels -r requirements.txt
# 실행 단계
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /app/wheels /app/wheels
COPY . .
RUN pip install --no-cache-dir --no-index --find-links=/app/wheels -r requirements.txt && \
rm -rf /app/wheels
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
멀티 스테이지 빌드 분석
위 Dockerfile의 내용을 분석해보자.
# 빌드 단계
FROM python:3.13 AS builder
WORKDIR /app
COPY requirements.txt .
# 의존성을 wheel 형태로 빌드
RUN pip wheel --no-cache-dir --wheel-dir /app/wheels -r requirements.txt
# 실행 단계
FROM python:3.13-slim
WORKDIR /app
# 빌드 단계에서 생성된 wheel 파일만 복사
COPY --from=builder /app/wheels /app/wheels
COPY . .
# wheel 파일에서 패키지 설치 후 wheel 파일 삭제
RUN pip install --no-cache-dir --no-index --find-links=/app/wheels -r requirements.txt && \
rm -rf /app/wheels
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
이 방식에서는:
- 컴파일러와 개발 도구는 빌드 단계에서만 사용되고 최종 이미지에는 포함되지 않는다.
- 미리 컴파일된 wheel 파일만 최종 이미지로 복사되므로 컴파일 과정의 중간 파일이 제외된다.
- 최종 이미지는 더 작은 기본 이미지(python:3.13-slim)를 사용하여 크기가 더 작다(종종 50-70% 감소).
- 불필요한 빌드 도구가 없어 보안이 강화된다.
단계별 분석
1. 빌드 단계 (Build Stage)
# 빌드 단계
FROM python:3.13 AS builder
여기서는 표준 Python 3.13 이미지를 기반으로 하는 첫 번째 단계를 시작하고, 이 단계에 builder라는 이름을 부여한다. 이 이름은 후속 단계에서 이 단계를 참조할 때 사용된다.
WORKDIR /app
COPY requirements.txt .
작업 디렉토리를 설정하고 필요한 의존성 파일을 복사한다.
RUN pip wheel --no-cache-dir --wheel-dir /app/wheels -r requirements.txt
휠(wheel)이 중요한 이유:
- 많은 파이썬 패키지(numpy, pandas, psycopg2 등)는 C/C++ 확장을 포함하고 있어 컴파일이 필요하다.
- 휠은 이미 컴파일된 바이너리 형식이므로, 설치 시 컴파일 과정이 필요하지 않다.
- 휠을 사용하면 빌드 도구(gcc, g++ 등)와 개발 헤더 파일 없이도 패키지를 설치할 수 있다.
2. 실행 단계 (Runtime Stage)
FROM python:3.13-slim
두 번째 단계는 python:3.13-slim이라는 더 작은 기본 이미지로 시작한다. 이 이미지는 표준 Python 이미지보다 훨씬 작으며, 필수적인 Python 런타임만 포함하고 있다.
WORKDIR /app
COPY --from=builder /app/wheels /app/wheels
이 명령은 첫 번째 단계(builder)에서 생성한 휠 파일들을 현재 단계로 복사한다. --from=builder는 이 파일들의 출처가 builder 단계라는 것을 Docker에 알려준다.
COPY . .
현재 호스트 시스템의 모든 애플리케이션 코드를 복사한다.
RUN pip install --no-cache-dir --no-index --find-links=/app/wheels -r requirements.txt && \
rm -rf /app/wheels
복사된 휠 파일을 사용하여 의존성을 설치한 다음, 휠 디렉토리를 삭제하여 이미지 크기를 더 줄인다.
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
마지막으로 컨테이너가 사용할 포트를 노출하고, FastAPI 애플리케이션을 Uvicorn 서버로 실행하는 명령을 지정한다.
멀티 스테이지 빌드의 장점
1. 이미지 크기 최소화
멀티 스테이지 빌드의 가장 큰 장점은 최종 이미지 크기의 대폭 감소이다. 빌드 도구, 컴파일러, 개발 헤더 파일 등 런타임에 필요하지 않은 모든 것이 최종 이미지에서 제외된다.
2. 보안 강화
더 작은 이미지는 보안 측면에서도 이점을 제공한다. 불필요한 도구와 라이브러리가 없으므로 공격 표면이 줄어들고, 취약점 발생 가능성도 낮아진다. 또한 빌드 과정에서 발생할 수 있는 보안 이슈가 최종 이미지에 영향을 미치지 않는다.
3. 구성 단순화
빌드 단계와 실행 단계를 별도의 Dockerfile로 나누는 대신, 하나의 파일로 모든 것을 관리할 수 있다. 이는 CI/CD 파이프라인과 배포 프로세스를 단순화한다.
4. 빌드 캐시 최적화
각 빌드 단계는 Docker의 레이어 캐싱 시스템을 활용한다. 소스 코드만 변경된 경우, 의존성 빌드 단계는 캐시에서 재사용되어 빌드 시간이 단축된다.
결론
멀티 스테이지 빌드는 Docker 이미지 최적화를 위한 강력한 도구이다. 특히 Django, FastAPI와 같은 파이썬 백엔드 프레임워크를 사용하는 개발자에게 큰 이점을 제공한다. 더 작고, 더 안전하며, 더 효율적인 컨테이너를 만들어 배포 프로세스를 개선하고 리소스 사용을 최적화할 수 있다.
빌드 도구와 중간 파일을 효과적으로 제거하는 멀티 스테이지 빌드의 접근 방식은 특히 C 확장을 포함하는 파이썬 패키지를 사용하는 애플리케이션에서 큰 차이를 만든다. 이를 통해 개발 환경과 실행 환경을 명확히 분리하고, 필요한 결과물만 최종 이미지에 포함시킬 수 있게 된다.