본문 바로가기

DevOps/Docker

[Docker] Docker로 Spring Boot 배포 시 이미지 크기 절반으로 줄이기

들어가며

Spring Boot 애플리케이션을 Docker로 배포할 때, 별다른 고민 없이 JDK 이미지를 베이스로 쓰는 경우가 많다. 그런데 이미지 크기를 확인해보면 400MB가 넘어가기도 한다. CI/CD 파이프라인에서 빌드와 푸시가 반복될수록, 클라우드 인스턴스에서 pull이 잦을수록 이 크기 차이가 누적되어 배포 속도와 비용에 영향을 준다.

 

결론부터 말하면, JDK 대신 JRE 이미지를 쓰는 것만으로 이미지 크기를 절반 가까이 줄일 수 있다. 이 글에서는 왜 JRE만으로 충분한지, 멀티 스테이지 빌드는 어떻게 적용하는지, 그리고 JRE 환경에서 운영 중 디버깅은 어떻게 하는지 정리한다.

 


1. JDK vs JRE, 뭐가 다른가?

  • JDK (Java Development Kit): 컴파일(javac) + 실행(java) + 디버깅 도구(jstack, jmap 등)
  • JRE (Java Runtime Environment): 실행(java)만 포함

 

핵심은 간단하다. Spring Boot를 ./gradlew bootJar 또는 mvn package로 빌드하면, 결과물인 jar 파일 안에는 이미 컴파일이 완료된 바이트코드(.class)와 내장 톰캣, 의존성 라이브러리가 모두 패키징되어 있다. 이걸 fat jar라고 부른다.

Docker 컨테이너에서는 이 fat jar를 java -jar실행만 하면 된다. 컴파일러(javac)가 필요 없으니 JRE면 충분하다.

[빌드 환경 - JDK 필요]
.java 소스 → javac 컴파일 → .class 바이트코드 → fat jar 패키징

[실행 환경 - JRE면 충분]
app.jar (내장 톰캣 + 의존성 포함) → java -jar 실행

 


2. 실제로 얼마나 차이 나는가?

eclipse-temurin:17 기준으로 비교해보면:

구분 JDK 이미지 JRE 이미지
이미지 크기 ~400MB+ ~200MB 내외
포함 도구 javac, jdb, jmap, jstack 등 java 런타임만
보안 공격 표면 넓음 (불필요한 도구 포함) 좁음
배포 속도 느림 빠름

 

직접 확인해볼 수도 있다.

docker images | grep temurin
# eclipse-temurin   17-jdk   ...   410MB
# eclipse-temurin   17-jre   ...   206MB

이미지가 절반으로 줄어든다. 이게 의미하는 건:

  • docker pull 속도가 빨라짐 → 배포 시간 단축
  • Docker Hub나 Container Registry 저장 용량 절약
  • 불필요한 도구가 없으니 CVE(보안 취약점) 노출 범위가 줄어듦

 


3. 멀티 스테이지 빌드로 적용하기

Docker 하나의 Dockerfile 안에서 빌드와 실행 환경을 분리할 수 있다. 빌드에는 JDK, 실행에는 JRE만 사용하면 최종 이미지에 소스 코드, Gradle/Maven, 빌드 캐시 같은 것들이 포함되지 않는다.

# Stage 1: 빌드 (JDK 필요)
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /build
COPY . .
RUN ./gradlew bootJar

# Stage 2: 실행 (JRE만)
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/build/libs/app.jar app.jar
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]

Stage 1(builder)은 최종 이미지에 포함되지 않는다. Stage 2에는 JRE와 jar 파일만 들어가므로 이미지가 가볍다.

 

Maven을 쓴다면

# Stage 1: 빌드
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /build
COPY . .
RUN ./mvnw package -DskipTests

# Stage 2: 실행
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]

 


4. CI에서 빌드한다면 더 간단하다

GitHub Actions, Jenkins 등 CI 환경에서 이미 Gradle/Maven 빌드를 수행하고 jar를 만드는 구조라면, 멀티 스테이지까지 갈 필요 없다. JRE 이미지에 jar만 복사하면 된다.

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY build/libs/*.jar app.jar
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]

이 경우 Dockerfile이 단순해지고, CI에서의 빌드 캐시(Gradle cache 등)를 활용할 수 있어 전체 파이프라인 속도가 더 빠를 수 있다.

 

어떤 방식을 선택할까?

방식 장점 단점
멀티 스테이지 빌드 Dockerfile 하나로 빌드~배포 완결 Docker 빌드 시 Gradle 캐시 활용이 어려움
CI 빌드 + JRE COPY CI 캐시 활용 가능, Dockerfile 단순 CI와 Docker가 분리되어 관리 포인트 증가

개인적으로는 CI 파이프라인이 이미 잘 갖춰져 있다면 후자가 실무에서 더 효율적이다.

 


5. JRE 환경에서 운영 중 디버깅

JRE만 넣으면 jstack, jmap, jcmd 같은 JDK 진단 도구를 못 쓰는 게 아닌가 하는 걱정이 들 수 있다. 맞다. 기본 상태에서는 사용할 수 없다.

 

하지만 장애가 발생했을 때 컨테이너에 접속해서 임시로 설치하면 된다.

docker exec -it <container> bash
apt-get update && apt-get install -y openjdk-17-jdk-headless
jstack 1       # PID 1의 스레드 덤프
jmap -heap 1   # 힙 메모리 상태 확인
jcmd 1 VM.flags  # 현재 JVM 옵션 확인

평소에는 가볍게 운영하고, 필요할 때만 도구를 설치하는 것이 합리적이다. 진단 도구를 항상 포함시키려고 JDK 이미지를 쓰는 건, 소화기 들고 다니려고 트럭을 모는 것과 비슷하다.

 

Spring Boot Actuator 활용

JDK 도구 없이도 Spring Boot Actuator를 통해 많은 정보를 얻을 수 있다.

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, threaddump, heapdump
# 컨테이너 밖에서 curl로 확인 가능
curl http://localhost:8080/actuator/health
curl http://localhost:8080/actuator/threaddump
curl http://localhost:8080/actuator/metrics/jvm.memory.used

Actuator의 threaddumpjstack과 비슷한 정보를 제공하고, heapdump는 힙 덤프 파일을 다운로드할 수 있다. 대부분의 운영 진단은 이것만으로 충분하다.

 


6. 최종 권장 Dockerfile

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY build/libs/*.jar app.jar
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]
# docker-compose.yml
services:
  app:
    build: .
    environment:
      - JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200

 


정리

항목 핵심
JRE만 쓰는 이유 fat jar에는 컴파일된 바이트코드가 들어있어 실행만 하면 된다
이미지 크기 변화 JDK ~400MB → JRE ~200MB, 절반으로 줄어든다
멀티 스테이지 빌드 빌드는 JDK, 실행은 JRE로 분리해 최종 이미지를 가볍게
CI 빌드 방식 CI에서 jar를 만들고 JRE에 COPY만 하면 더 단순하다
운영 디버깅 필요 시 JDK 임시 설치하거나 Actuator를 활용한다

이미지 크기를 줄이는 건 단순히 디스크를 아끼는 문제가 아니다. 배포 속도, 보안, 운영 비용 모두에 영향을 주는 기본기다.

 


참고 자료

'DevOps > Docker' 카테고리의 다른 글

[Docker] 왜 내 JAVA_OPTS는 Docker에서 적용이 안 될까?  (0) 2026.03.02