Profile picture

[Dockerfile] 멀티 스테이지 빌드 (Multi Stage build)

JaehyoJJAng2023년 04월 13일

▶︎ Multi Stage 활용사례

‣ 도커 이미지 경량화

가장 많이 활용하는 사례는 도커 이미지의 사이즈를 줄이는 것이다. 모든 도커 이미지에 대한 사이즈를 줄일 수는 없고 빌드 단계가 있는 도커 이미지 등 단계가 분리되어 있는 Dockerfile만 적용 가능하다.

아래 예제는 Multi Stage를 사용했을 경우 도커 이미지 크기가 얼마나 줄었는지 보여주는 예시이다.
image

💡 Why?

도커 이미지를 왜 줄여야 하는걸까?

바로 도커 이미지 관리 시 저장 장치(하드 디스크 ..) 용량을 줄이기 위해서이다.

개발자가 아닌 서버 관리자 입장에서 도커 이미지 크기를 줄이는 것은 매우 중요하다. 애플리케이션 버전 별로 도커 이미지를 관리해야 하는데, 도커 이미지가 개당 500MB라고 한다면 그 용량을 어떻게 감당 하겠는가?.


‣ 빌드 속도 향상

Multi Stage를 사용하면 Dockerfile 작업을 병렬로 실행하여 빌드 속도를 향상 시킬 수 있다. 단, 병렬로 실행하고자 하는 작업에 연관관계가 없어야 한다!


▶︎ Docker build

Multi Stage는 Docker build의 옵션 기능이다. 그러므로 Docker build가 무엇인지 이해하고 있어야 한다.

Docker build는 간단하다. Docker build는 Dockerfile에 적힌 내용을 해석하여 도커 이미지를 만드는 일련의 과정이다.
image


▶︎ Multi Stage

Multi Stage는 여러 개의 Base Image를 사용하여 docker build를 수행한다. Base Image는 Docker build에 사용하는 기본이미지이다. Dockerfile에서 FROM에 설정된 이미지를 뜻한다.

FROM 키워드를 기준으로 작업공간이 분리되는데, 분리된 작업 공간을 stage라고 칭한다. Multi Stage는 stage가 2개 이상일 때 해당된다
image

위 예제를 docker build로 실행하면 docker build 로그에서 stage가 출력된다. stage가 2개 이므로 2개의 작업 영역이 분리되는 셈이다.

stage가 여러개라고 해서 도커이미지가 여러개 생성되는 거는 아니다. 가장 마지막에 실행된 stage 작업이 도커 이미지로 생성된다

아래 예제는 우분투 정보를 출력하는 Dockerfile입니다. 우분투18, 우분투16버전 정보를 출력하지만, 가장 마지막에 실행된 우분투16버전 출력이 도커이미지로 생성된다. 예제를 살펴보자
image


‣ COPY --from

  • COPY --from 구문은 멀티스테이지 빌드에서 이전 단계의 이미지에서 파일이나 디렉토리를 복사할 때 사용된다.

간단한 예제를 통해 설명해보겠다.

예를 들어, 웹 애플리케이션을 빌드하는데 필요한 두 가지 단계가 있다고 가정해보자.

첫번째 단계에서는 소스 코드를 빌드하고,

두번째 단계에서는 정적 파일과 실행 가능한 애플리케이션을 생성하도록 코드를 작성해보자.

# 첫번째 단계: 빌드
FROM node:alpine AS builder

WORKDIR /app

COPY ./package*.json .
RUN npm install

COPY . .
RUN npm run build

# 두번째 단계: 애플리케이션 실행
FROM nginx:alpine
WORKDIR /usr/share/nginx/html

# 이전 단계의 빌더 이미지에서 생성된 파일들을 복사
COPY --from=builder /app/build .

위의 코드에서 첫 번째 단계에서는 Node.js를 사용하여 소스 코드를 빌드한다.

이 단계에서 생성된 이미지는 builder로 태그를 지정해주었다.

두 번째 단계에서는 Nginx를 사용하여 정적 파일을 제공하는데, 여기서 COPY --from=builder /app/build . 구문을 사용하여 이전 단계에서 생성된 builder 이미지에서 /app/build 디렉토리의 파일들을 현재 작업 디렉토리로 복사하였다.


▶︎ 멀티 스테이지 실습

Multi Stage를 적용하지 않으면 stage 한 개에 모든 작업 내용이 실행된다. 그러므로 최종 명령어를 실행하기 위한 불필요한 것들이 하나의 도커 이미지에 다 들어가게 되는 것이다.


‣ 자바 앱 빌드

아래 예제는 Java Application을 빌드해 jar를 실행하는 예제이다.

FROM openjdk:8 AS init-build
USER root
LABEL description="Java App Builder"
RUN git clone https://github.com/iac-source/inbuilder.git
RUN mv ./inbuilder /builder
WORKDIR /builder
RUN chmod 700 mvnw && ./mvnw clean package

WORKDIR target
EXPOSE 60434
ENTRYPOINT [ "java" , "-jar", "app-in-host.jar" ]

Multi Stage를 적용하면 Build에 필요한 것들을 분리할 수 있다.

FROM openjdk:8 AS init-build
USER root
LABEL description="Java App Builder"
RUN git clone https://github.com/iac-source/inbuilder.git
RUN mv ./inbuilder /builder
WORKDIR /builder
RUN chmod 700 mvnw && ./mvnw clean package


FROM gcr.io/distroless/java:8 as execution
LABEL description="Echo IP Java App"
EXPOSE 60434
COPY --from=init-build /builder/target/app-in-host.jar /opt/app-in-image.jar
WORKDIR /opt
ENTRYPOINT [ "java" , "-jar", "app-in-image.jar" ]

docker build를 하면 stage 한 개를 사용하는 것보다 이미지 크기가 엄청나게 줄어든 것을 확인할 수 있을거다

$ docker build --tag <이미지이름>:tag .

사이즈 비교는 1.1 도커 이미지 경량화에서 확인할 수 있다.


‣ 노드 앱 빌드

# Build Stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY ./package*.json ./
RUN npm install -D
COPY ./ ./
RUN npm run build

# Production Stage
FROM node:18-alpine AS production
COPY --from=builder ./build ./build
COPY --from=builder ./package*.json ./
RUN npm install --only=production
CMD [ "npm", "run", "start" ]

이 Dockerfile은 두 개의 단계로 구성됩니다. 첫 번째 단계(build)는 애플리케이션의 소스 코드를 가져와 필요한 종속성을 설치하고 프로덕션 빌드를 실행합니다. 이 빌드된 결과물은 두 번째 단계(production)에서 가져와 최종 이미지를 생성합니다.

  • FROM node:18-alpine AS builder: Node.js 18 Alpine 이미지를 빌드 단계의 기본 이미지로 사용합니다.
  • WORKDIR /app: 작업 디렉토리를 /app으로 설정합니다.
  • COPY package*.json ./: 필요한 패키지 파일을 복사하여 종속성을 설치합니다.
  • RUN npm install: 필요한 npm 종속성을 설치합니다.
  • COPY . .: 소스 코드를 현재 작업 디렉토리로 복사합니다.
  • RUN npm run build: 프로덕션 빌드를 실행합니다.

그 다음, 빌드된 애플리케이션만을 가지고 두 번째 단계에서 최종 이미지를 생성합니다.

  • FROM node:18-alpine AS production: Node.js 18 Alpine 이미지를 실행 단계의 기본 이미지로 사용합니다.
  • WORKDIR /app: 작업 디렉토리를 /app으로 설정합니다.
  • COPY --from=builder ./build ./build: 빌드된 애플리케이션 파일을 복사합니다.
  • COPY --from=builder ./package*.json ./: 필요한 패키지 파일을 복사합니다.
  • RUN npm install --only=production: 프로덕션 환경에서 필요한 npm 종속성을 설치합니다.
  • CMD ["npm", "run", "start"]: 컨테이너가 시작될 때 실행될 명령을 정의합니다.

이러한 방식으로 Dockerfile을 구성하면, 빌드 과정과 실행 과정이 멀티 스테이지로 나눠지며, 최종 이미지에는 필요한 파일만 포함하여 이미지 크기를 줄일 수 있습니다.


Loading script...