스프링 프로젝트를 .jar 파일로 빌드하는 것은 첫 단계이다. 이 .jar 파일을 다른 환경에서 실행하려면 해당 환경에 정확한Java(JVM) 버전이 설치되어 있어야 한다. Docker는 이러한 환경 종속성 문제를 해결하기 위해 .jar 파일과 필요한 Java 런타임을 하나로 묶어 Docker 이미지로 만드는 방법을 제공한다
Dockerfile – 이미지 빌드의 설계도
Docker 이미지를 빌드하려면 Dockerfile 이라는 스크립트 파일이 필요하다. Dockerfile은 확장자가 없으며, 관례상 Dockerfile 이라는 이름으로, 프로젝트 루트 디렉토리에 위치시키는 것이 일반적이다
- Dockerfile의 역할: 이미지 생성에 필요한 모든 명령어(기반 이미지, 파일 복사, 명령 실행, 포트 노출 등)를 순서대로 정의한다
- 빌드 컨텍스트(Build Context): Docker 이미지를 빌드할 때 Docker 데몬이 참조할 수 있는 모든 파일과 디렉터리의 위치를 의미한다. 일반적으로 Dockerfile이 위치한 프로젝트의 루프 디렉토리가 빌드 컨텍스트가 된다
docker build 명령어
- Dockerfile을 사용하여 이미지를 빌드하는 명령어는 아래와 같다
<> Bash
docker build -t [이미지명]:[태그명] [빌드_컨텍스트_경로]
# 예시: docker build -t my-spring:v1.0 .
- -t [이미지명]:[태그명]: 빌드될 이미지의 이름과 버전을 지정한다 (예: my-spring:v1.0)
- -f [Dockerfile_경로] (선택 사항): Dockerfile이 현재 디렉토리에 없거나 이름이 Dockerfile이 아닌 경우에 사용한다 (예: -f my-dockerfile)
- [빌드_컨텍스트_경로]: Docker가 빌드 시 참조할 파일들이 있는 디렉토리 경로를 지정한다. 보통 Dockerfile이 있는 현재 디렉토리를 의미하는 . 을 사용한다
Spring Boot 애플리케이션을 위한 Dockerfile 예시 분석
스프링 부트 .jar 파일과 Java 런타임을 포함하는 Dockerfile은 보통 멀티 스테이지 빌드(Mulit-stage build) 방식을 사용한다. 이는 최종 이미지 크기를 최적화하고 보안을 강화하는데 효과적이다
<> Dockerfile
# 1단계: 빌드 스테이지 (build stage)
# 필요한 프로그램 설치: openjdk:17-jdk-alpine 이미지를 기반으로 시작한다
# 'as stage1'은 이 스테이지에 'stage1'이라는 별칭을 부여한다
FROM openjdk:17-jdk-alpine as stage1
# 작업 디렉토리 설정: 컨테이너 내부의 /app 디렉토리에서 모든 작업을 수행한다
WORKDIR /app
# 파일 복사: 로컬 프로젝트의 빌드 관련 파일들을 컨테이너 내부의 /app으로 복사한다
# 이 과정은 리눅스 환경에서 빌드하기 위함이다
COPY gradle gradle/
COPY src src/
COPY build.gradle .
COPY settings.gradle .
COPY gradlew .
# 빌드: gradlew에 실행 권한을 부여하고 bootJar 태스크를 실행하여 .jar 파일을 생성한다
RUN chmod +x gradlew
RUN ./gradlew bootJar
# 2단계: 최종 이미지 스테이지(final image stage)
# 새로운 경량 openjdk:17-jdk-alpine 이미지를 기반으로 시작한다
# 이 스테이지에는 불필요한 빌드 도구나 소스 코드가 포함되지 않는다
FROM openjdk:17-jdk-alpine
# 작업 디렉토리 설정: 컨테이너 내부의 /app 디렉토리에서 작업을 수행한다
WORKDIR /app
# .jar 파일 복사: 이전 'stage1'에서 빌드된 .jar 파일을 최종 이미지로 복사한다
# 'COPY --from=stage1' 구문을 사용하여 이전 스테이지의 결과물만 가져온다
# 'app.jar'라는 이름으로 복사된다
COPY --from=stage1 /app/build/libs/*.jar app.jar
# 실행 명령어: 이 이미지를 기반으로 컨테이너가 실행될 때 자동으로 실행될 명령어를 정의한다
# 배열 형태는 공백을 포함하는 명령어를 안전하게 실행하는 데 권장된다
ENTRYPOINT ["java", "-jar", "app.jar"]
Dockerfile 스크립트 해설
- FROM: 베이스 이미지(기반 이미지)를 지정한다. 여기서는 openjdk:17-jdk-alpine을 사용한다. alpine 버전을 매우 경량화된 리눅스 배포판으로 이미지 크기를 줄이는 데 효과적이다
- 실제 build시 에러가 나왔으니 적절한 환경에 맞게 설정을 해주어야 한다
- WORKDIR: 컨테이너 내부에서 명령어가 실행될 작업 디렉토리를 설정한다
- COPY: 호스트 시스템의 파일을 컨테이너 내부로 복사한다
- COPY gradle gradle/ : gradle 디렉토리를 gradle/ 디렉토리로 복사
- COPY build.gradle . : build.gradle 파일을 현재 작업 디렉토리(.)로 복사
- COPY –from=stage1 … : 멀티스테이지 빌드에서 이전 스테이지(stage1)의 결과물만 가져올 때 사용한다
- RUN: 이미지를 빌드하는 동안 컨테이너 내부에서 명령어를 실행한다. 주로 환경 설정이나 빌드 스크립트 실행에 사용된다 (예: chmod +x gradlew. ./gradlew bootJar)
- ENTRYPOINT 또는 CMD: 이미지를 기반으로 컨테이너가 시작될 때 실행될 명령어를 지정한다
- ENTRYPOINT: 컨테이너의 주 프로세스를 정의하며 컨테이너가 “실행 가능한 프로그램”처럼 동작하게 한다. 보통 고정된 실행 명령어를 사용한다
- CMD: ENTRYPOINT에 전달될 기본 인수를 제공하거나, ENTRYPOINT가 없을 때 기본 실행 명령어를 지정한다
멀티스테이지 빌드의 장점
- 이미지 크기 최적화: 빌드에 필요한 도구(Gradle, 소스 코드 등)를 최종 이미지에 포함시키지 않으므로, 최종 이미지의 크기가 훨씬 작아진다
- 보안 강화: 최종 이미지에 불필요한 파일이나 도구가 없어 공격 표면(attack surface)이 줄어들어 보안에 유리하다
- 빌드 환경 일관성: 리눅스 기반 컨테이너 환경에서 직접 빌드하므로, 로컬 OS 환경(Windows, macOS)과 환경의 불일치로 인한 잠재적 문제를 방지한다
빌드된 Docker 이미지 확인 및 실행
Dockerfile 작성을 완료했다면, docker build 명령어로 이미지를 생성한다
<> Bash
docker build -t my-spring:v1.0
- 빌드 과정에서 FROM openjdk:17-jdk-alpine과 같이 기반 이미지를 다운로드하므로 인터넷 연결이 필요하다
- jdk버전이 로컬 환경과 다르면 빌드 에러가 발생할 수 있으니 Dockerfile의 FROM 구문을 적절히 수정해야 한다
- 에러가 꼭 FROM에서 일어나지만은 않는다.
빌드 명령어를 입력하니 실제로 아래와 같은 에러가 나타났다
1 warning found (use docker --debug to expand): - FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 2)
2 | >>> FROM openjdk:17-jdk-alpine as stage1
ERROR: failed to solve: openjdk:17-jdk-alpine: failed to resolve source metadata for docker.io/library/openjdk:17-jdk-alpine: no match for platform in manifest: not found
AI에 질문을 해보니 아래와 같이 대답을 해주었다
# FROM eclipse-temurin:17-jdk-alpine as stage1 역시 에가 나왔다
FROM eclipse-temurin:17-jdk-jammy AS build # 변경해주니 에러가 나지 않았다.
- 지금 MacBook Pro(Apple Silicon, arm64)에서
openjdk:17-jdk-alpine
이미지를 쓰고 있는데, 이 태그는 현재 arm64 변형이 없어서 “no match for platform in manifest” 에러가 난다. 즉, 자바 버전 문제가 아니라 베이스 이미지가 내 호스트 아키텍처(arm64)에 맞는 매니페스트를 제공하지 않는 것이 핵심이다
이미지 빌드가 성공하면 docker images 명령어나 Docker Desktop 애플리케이션을 통해 생성된 이미지를 확인할 수 있다
<> Bash
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-spring v1.0 831930fa617c 15 minutes ago 749MB
이 이미지를 사용하여 Docker 컨테이너를 실행할 수 있다
