본문 바로가기

Cloud/Docker

[Docker] Multi-stage Build

  • Docker 이미지를 만들 때, 빌드 과정에서 사용하는 여러 패키지나 라이브러리가 최종 이미지에 포함되면 불필요하게 이미지 크기가 커질 수 있다.
  • 애플리케이션을 배포하기 위한 최종 이미지는 빌드에 사용된 도구나 개발 의존성을 포함할 필요가 없으므로 이런 부분을 제거하고 필요한 파일만을 포함시키는 것이 좋다.
  • 이를 해결하기 위해 Multi-stage 빌드(Multi-stage Build)를 사용한다.

 

Multi-stage 빌드란?

Multi-stage 빌드는 Dockerfile에서 여러 개의 스테이지를 정의하여 빌드 과정과 실행 환경을 분리하는 방법이다. 각 스테이지는 독립적으로 실행되며, 이전 스테이지에서 생성된 아티팩트를 다음 스테이지로 전달하는 방식으로 진행된다. 이 방식은 최종 이미지에 필요한 파일만 남기고 불필요한 빌드 파일이나 도구는 제외시킬 수 있어 이미지 크기를 최적화하는 데 큰 도움이 된다.

 

효과

  • 이미지 크기 최소화
  • 빌드 속도 최적화

 

활용 예

Vue 프로젝트를 Nginx로 배포하는 경우

  1. 빌드 단계 (Node.js 환경에서 Vue 애플리케이션 빌드)
    • 먼저 Node.js 환경에서 Vue 애플리케이션을 빌드한다. 이때 필요한 빌드 도구와 패키지는 첫 번째 스테이지에서만 사용된다.
  2. 실행 단계 (Nginx로 정적 파일 서빙)
    • 빌드된 정적 파일을 Nginx 서버로 복사하여 실행한다. 이때, Nginx는 빌드 도구가 아닌 정적 파일만을 서빙한다.

 

BEFORE (Multi-stage 적용 ❌)

# 1️⃣ 빌드 단계 (Build stage)
# Node.js 14 버전을 기반 이미지로 사용하여 애플리케이션을 빌드
FROM node:14

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

# package.json과 package-lock.json을 복사하여 의존성 설치
COPY package.json package-lock.json ./

# npm install을 실행하여 필요한 패키지를 설치
RUN npm install

# 애플리케이션의 소스 파일을 컨테이너에 복사
COPY . .

# 애플리케이션의 정적 파일을 생성하기 위해 build 디렉토리 생성
RUN mkdir -p /app/static && npm run build


# 2️⃣ 프로덕션 단계 (Production stage)
# 실행 환경은 Nginx를 사용하여 빌드된 애플리케이션 서빙
FROM nginx:alpine

# Nginx 설정 파일을 컨테이너의 /etc/nginx/nginx.conf로 복사
COPY nginx.conf /etc/nginx/nginx.conf

# 빌드 후 생성된 dist 디렉토리를 참조
COPY ./dist /usr/share/nginx/html

# Nginx가 사용할 포트 80을 외부에 노출
EXPOSE 80

# 컨테이너가 실행되면 Nginx 서버를 시작하도록 명령을 설정
CMD ["nginx", "-g", "daemon off;"]

 

 

AFTER (Multi-stage 적용 ⭕️)

# 1️⃣ 빌드 단계 (Build stage)
# Node.js 14 버전을 기반 이미지로 사용하여 애플리케이션을 빌드
FROM node:14 AS build

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

# package.json과 package-lock.json을 복사하여 의존성 설치
COPY package.json package-lock.json ./

# npm install을 실행하여 필요한 패키지를 설치
RUN npm install

# 애플리케이션의 소스 파일을 컨테이너에 복사
COPY . .

# 애플리케이션의 정적 파일을 생성하기 위해 build 디렉토리 생성
RUN mkdir -p /app/static && npm run build


# 2️⃣ 프로덕션 단계 (Production stage)
# 실행 환경은 Nginx를 사용하여 빌드된 애플리케이션 서빙
FROM nginx:alpine

# Nginx 설정 파일을 컨테이너의 /etc/nginx/nginx.conf로 복사
COPY nginx.conf /etc/nginx/nginx.conf

# 📍빌드 단계에서 생성된 dist 디렉토리를 Nginx의 웹 서버 디렉토리인 /usr/share/nginx/html로 복사
COPY --from=build /app/dist /usr/share/nginx/html

# Nginx가 사용할 포트 80을 외부에 노출
EXPOSE 80

# 컨테이너가 실행되면 Nginx 서버를 시작하도록 명령을 설정
CMD ["nginx", "-g", "daemon off;"]

 

 

비교

두 dockerfile간 차이를 찾기 매우 어려울 수 있다. 실제로 차이는 아주 미세하다. AS 명령어를 사용해 스테이지에 이름을 부여하고, 나중에 해당 스테이지에서 파일을 복사해 사용하는 것. 그 차이이다.

  • FROM node:14 AS build: 첫 번째 FROM 명령어는 빌드 환경을 정의하고, 이 환경에서 애플리케이션을 빌드한다. AS build는 이 스테이지에 이름을 부여하는데, 나중에 COPY --from=build에서 이 스테이지에서 파일을 복사할 수 있게 해준다.
  • COPY --from=build: 이 명령어는 빌드 단계에서 생성된 /app/dist 디렉토리만 복사하여 Nginx 컨테이너로 복사하고, 나머지 불필요한 파일들은 포함되지 않는다.

 

여담

실제 이미지로 돌려본 결과로는.. single stage의 이미지 사이즈가 더 작아서 당황스러웠다..

이유 알려주실 분...!

 

 

Multi-stage

Learn about multi-stage builds and how you can use them to improve your builds and get smaller images

docs.docker.com