Multi-Stage Build
2025. 12. 30. 20:39ㆍDocker
#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에서 압축을 풀었던 경로를 가리키는 내부 변수를 설정합니다.
이 구문들은 Stage 1(--from=build)에서 압축 해제된 파일들을 선별적으로 가져옵니다.
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib:
- 의존성 라이브러리(.jar 파일들)를 복사합니다. 가장 용량이 크지만 거의 바뀌지 않으므로 하단 레이어에 배치합니다.
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF:
- MANIFEST.MF 같은 설정 정보를 복사합니다.
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로 실행하는 것보다 압축이 풀린 상태에서 클래스 로더가 파일을 읽는 것이 메모리 관리와 시작 속도 측면에서 효율적입니다.
요약: 왜 이렇게 복잡하게 만들었나요?
- 빌드 속도 최적화: 코드를 한 줄만 수정했을 때, 수백 메가바이트의 라이브러리 레이어는 건드리지 않고 수 킬로바이트의 클래스 레이어만 새로 빌드합니다.
- 이미지 경량화: 빌드 과정에서 발생한 찌꺼기 파일 없이 최종 실행에 필요한 파일만 남깁니다.
- 성능: 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 |