Multi-Stage Build

2025. 12. 30. 20:39Docker

#stage 1

#Start with a base image containing Java runtime

FROM openjdk:11-slim as build



# Add Maintainer Info

LABEL maintainer="Illary Huaylupo <illaryhs@gmail.com>"



# The application's jar file

ARG JAR_FILE



# Add the application's jar to the container

COPY ${JAR_FILE} app.jar



#unpackage jar file

RUN mkdir -p target/dependency && (cd target/dependency; jar -xf /app.jar)



#stage 2

#Same Java runtime

FROM openjdk:11-slim



#Add volume pointing to /tmp

VOLUME /tmp



#Copy unpackaged application to new container

ARG DEPENDENCY=/target/dependency

COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib

COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF

COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app



#execute the application

ENTRYPOINT ["java","-cp","app:app/lib/*","com.optimagrowth.license.LicenseServiceApplication"]

 

1. 멀티 스테이지 빌드 (Multi-stage Build) 구조

위 파일은 하나의 Dockerfile 안에 FROM 구문이 두 번 등장합니다. 이것을 멀티 스테이지 빌드라고 합니다.

  • 목적: 빌드 환경(컴파일러, 빌드 도구 등)과 실행 환경(런타임)을 분리하여, 최종 이미지에는 실행에 필요한 최소한의 파일만 담아 용량을 줄이고 보안을 강화하는 것입니다.

 

2. Stage 1: Build (파일 전처리 단계)

이 단계에서는 원본 JAR 파일의 압축을 풀어 실행 효율을 높이는 작업을 수행합니다.

 

FROM openjdk:11-slim as build

  • openjdk:11-slim: Java 11 실행 환경이 포함된 가벼운 데비안 리눅스 기반 이미지입니다.
  • as build: 이 단계를 build라는 이름으로 부르겠다고 선언합니다. 이후 Stage 2에서 이 단계의 결과물을 가져올 때 사용됩니다.

LABEL maintainer="Illary Huaylupo <illaryhs@gmail.com>"

  • 이미지의 메타데이터를 추가합니다. 관리자가 누구인지 명시하여 유지보수를 돕습니다.

ARG JAR_FILE

  • ARG(Argument): docker build 명령을 실행할 때 사용자가 외부에서 넘겨줄 수 있는 변수입니다.
  • 예: --build-arg JAR_FILE=target/app-0.0.1.jar와 같이 사용하여 유연하게 빌드할 수 있습니다.

COPY ${JAR_FILE} app.jar

  • 호스트 머신(컴퓨터)에 있는 실제 JAR 파일을 컨테이너 내부의 루트(/) 디렉토리에 app.jar라는 이름으로 복사합니다.

RUN mkdir -p target/dependency && (cd target/dependency; jar -xf /app.jar)

  • 의미: target/dependency 폴더를 만들고, 그 안으로 이동한 뒤 jar -xf 명령어로 app.jar의 압축을 풉니다.
  • 왜 압축을 푸는가?: Spring Boot JAR 파일은 내부에 라이브러리(lib), 설정(META-INF), 실제 코드(classes)를 모두 갖고 있는 'Fat JAR' 형태입니다. 이를 미리 풀어놓으면 Docker의 레이어 캐싱(Layer Caching) 기능을 극대화할 수 있습니다. (코드가 바뀌어도 라이브러리 레이어는 재사용됨)

 

3. Stage 2: Production (최종 실행 단계)

실제 서비스에서 구동될 "진짜" 이미지입니다.

FROM openjdk:11-slim

  • 다시 깨끗한 베이스 이미지로 시작합니다. Stage 1에서 썼던 임시 파일이나 도구들은 여기 포함되지 않습니다.

VOLUME /tmp

  • 컨테이너 내의 /tmp 디렉토리를 호스트와 공유하거나 임시 저장소로 지정합니다.
  • 이유: Spring Boot 내장 톰캣(Tomcat)은 작업 시 /tmp 폴더를 사용합니다. 컨테이너의 쓰기 가능한 레이어에 직접 쓰는 것보다 볼륨을 사용하는 것이 성능상 유리합니다.

ARG DEPENDENCY=/target/dependency

  • Stage 1에서 압축을 풀었던 경로를 가리키는 내부 변수를 설정합니다.

COPY --from=build ... (중요)

이 구문들은 Stage 1(--from=build)에서 압축 해제된 파일들을 선별적으로 가져옵니다.

  1. COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib:
  • 의존성 라이브러리(.jar 파일들)를 복사합니다. 가장 용량이 크지만 거의 바뀌지 않으므로 하단 레이어에 배치합니다.
  1. COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF:
  • MANIFEST.MF 같은 설정 정보를 복사합니다.
  1. COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app:
  • 사용자가 작성한 실제 Java 클래스 파일들을 복사합니다. 가장 자주 수정되는 부분이므로 마지막에 복사하여 빌드 시간을 단축합니다.

 

4. 실행 설정

ENTRYPOINT ["java","-cp","app:app/lib/*","com.optimagrowth.license.LicenseServiceApplication"]

  • 컨테이너가 시작될 때 실행될 명령어입니다.
  • -cp (Classpath): Java가 클래스를 찾을 경로를 지정합니다.
  • app: 현재 소스 코드 위치
  • app/lib/*: 모든 라이브러리 위치
  • com.optimagrowth...: 실행될 메인 클래스(Main Class)의 풀 경로입니다.
  • 장점: java -jar로 실행하는 것보다 압축이 풀린 상태에서 클래스 로더가 파일을 읽는 것이 메모리 관리와 시작 속도 측면에서 효율적입니다.

요약: 왜 이렇게 복잡하게 만들었나요?

  1. 빌드 속도 최적화: 코드를 한 줄만 수정했을 때, 수백 메가바이트의 라이브러리 레이어는 건드리지 않고 수 킬로바이트의 클래스 레이어만 새로 빌드합니다.
  2. 이미지 경량화: 빌드 과정에서 발생한 찌꺼기 파일 없이 최종 실행에 필요한 파일만 남깁니다.
  3. 성능: Spring Boot 애플리케이션을 "언팩(Unpacked)" 형태로 실행하여 런타임 효율을 높였습니다.

'Docker' 카테고리의 다른 글

Docker Layer  (0) 2025.12.30
COPY --from=build  (0) 2025.12.30
CMD vs ENTRYPOINT  (0) 2025.12.30
Docker Hub에서 Docker Image 삭제 방법  (0) 2024.12.06
Docker 커맨드와 컨셉 이해  (0) 2024.12.06