Java 언어별 가이드
Java 언어별 가이드
이 가이드는 Docker로 컨테이너화된 Spring Boot 어플리케이션을 만드는 방법을 알려줘요.
출처: 문서
본문
이 가이드의 Java getting started 모듈에서는 Docker를 사용해 Spring Boot 어플리케이션을 컨테이너화하는 방법을 배워요. 이 모듈에서 다음과 같은 것들을 배울 수 있어요:
- Maven으로 Spring Boot 어플리케이션을 컨테이너화하고 실행하기
- 로컬 개발 환경을 구성해 데이터베이스를 컨테이너에 연결하고, 디버거를 설정하며, Compose Watch로 라이브 리로드를 사용하기
- 단위 테스트를 컨테이너 안에서 실행하기
Java getting started 모듈을 모두 마치면, 이 가이드의 예제와 설명을 바탕으로 자신만의 Java 어플리케이션을 컨테이너화할 수 있게 돼요.
Java 어플리케이션 컨테이너화하기
준비 사항 (Prerequisites)
- 최신 버전의 Docker Desktop을 설치했어야 해요. Docker는 자주 새 기능을 추가하므로, 이 가이드의 일부 내용은 최신 버전의 Docker Desktop에서만 동작할 수 있어요.
- Git 클라이언트가 필요해요. 이 섹션의 예제는 명령줄 기반의 Git 클라이언트를 사용하지만, 어떤 클라이언트를 써도 무방해요.
개요 (Overview)
이 섹션에서는 Java 어플리케이션을 컨테이너화하고 실행하는 과정을 살펴봐요.
샘플 어플리케이션 가져오기
로컬 개발 머신에 사용할 샘플 어플리케이션을 클론해요. 터미널에서 다음 명령을 실행해 저장소를 복제하세요.
$ git clone https://github.com/spring-projects/spring-petclinic.git
샘플 어플리케이션은 Maven으로 빌드한 Spring Boot 어플리케이션이에요. 자세한 내용은 저장소의 readme.md를 참고해요.
Docker 자산 만들기
이제 어플리케이션이 준비됐으니, 어플리케이션을 컨테이너화하는 데 필요한 Docker 자산을 만들 수 있어요.
[!TIP]
Gordon, Docker의 AI 어시스턴트가 프로젝트에 맞는 Docker 자산을 생성해줄 수 있어요. Gordon에게 어플리케이션에 맞춘 Dockerfile, Compose 파일,
.dockerignore를 만들어 달라고 요청해보세요.
Dockerfile이라는 파일을 다음 내용으로 만들어요.
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
################################################################################
# Create a stage for resolving and downloading dependencies.
FROM eclipse-temurin:21-jdk-jammy as deps
WORKDIR /build
# Copy the mvnw wrapper with executable permissions.
COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.m2 so that subsequent builds don't have to
# re-download packages.
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests
################################################################################
# Create a stage for building the application based on the stage with downloaded dependencies.
# This Dockerfile is optimized for Java applications that output an uber jar, which includes
# all the dependencies needed to run your app inside a JVM. If your app doesn't output an uber
# jar and instead relies on an application server like Apache Tomcat, you'll need to update this
# stage with the correct filename of your package and update the base image of the "final" stage
# use the relevant app server, e.g., using tomcat (https://hub.docker.com/_/tomcat/) as a base image.
FROM deps as package
WORKDIR /build
COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
################################################################################
# Create a stage for extracting the application into separate layers.
# Take advantage of Spring Boot's layer tools and Docker's caching by extracting
# the packaged application into separate layers that can be copied into the final stage.
# See Spring's docs for reference:
# https://docs.spring.io/spring-boot/docs/current/reference/html/container-images.html
FROM package as extract
WORKDIR /build
RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted
################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the install or build stage where the necessary files are copied
# from the install stage.
#
# The example below uses eclipse-turmin's JRE image as the foundation for running the app.
# By specifying the "17-jre-jammy" tag, it will also use whatever happens to be the
# most recent version of that tag when you build your Dockerfile.
# If reproducibility is important, consider using a specific digest SHA, like
# eclipse-temurin@sha256:99cede493dfd88720b610eb8077c8688d3cca50003d76d1d539b0efc8cca72b4.
FROM eclipse-temurin:21-jre-jammy AS final
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
# Copy the executable from the "package" stage.
COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./
EXPOSE 8080
ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]
[!NOTE] 샘플 저장소에는
docker-compose.yml파일이 포함돼 있어요. 다음 설명에서는 선호되는compose.yaml파일명을 사용해요 — 두 이름 모두 Docker Compose에서 지원돼요.
compose.yaml이라는 파일을 다음 내용으로 만들어요.
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker Compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/
# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
server:
build:
context: .
ports:
- 8080:8080
# The commented out section below is an example of how to define a PostgreSQL
# database that your application can use. `depends_on` tells Docker Compose to
# start the database before your application. The `db-data` volume persists the
# database data between container restarts. The `db-password` secret is used
# to set the database password. You must create `db/password.txt` and add
# a password of your choosing to it before running `docker compose up`.
# depends_on:
# db:
# condition: service_healthy
# db:
# image: postgres:18
# restart: always
# user: postgres
# secrets:
# - db-password
# volumes:
# - db-data:/var/lib/postgresql
# environment:
# - POSTGRES_DB=example
# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
# expose:
# - 5432
# healthcheck:
# test: [ "CMD", "pg_isready" ]
# interval: 10s
# timeout: 5s
# retries: 5
# volumes:
# db-data:
# secrets:
# db-password:
# file: db/password.txt
.dockerignore이라는 파일을 다음 내용으로 만들어요.
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose.y*ml
**/target
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/vendor
LICENSE
README.md
이제 spring-petclinic 디렉토리에 다음 세 파일이 있어야 해요.
어플리케이션 실행하기
spring-petclinic 디렉토리 안에서 터미널에 다음 명령을 실행해요.
$ docker compose up --build
앱을 처음 빌드·실행할 때 Docker가 의존성을 다운로드하고 앱을 빌드해요. 네트워크 연결 상태에 따라 몇 분이 걸릴 수 있어요.
브라우저를 열고 http://localhost:8080에서 어플리케이션을 확인해요. 펫 클리닉 어플리케이션을 볼 수 있어요.
터미널에서 ctrl+c를 눌러 어플리케이션을 중지해요.
어플리케이션을 백그라운드로 실행하기
-d 옵션을 추가하면 터미널에서 분리된 상태(detached)로 어플리케이션을 실행할 수 있어요. spring-petclinic 디렉토리 안에서 터미널에 다음 명령을 실행해요.
$ docker compose up --build -d
브라우저를 열고 http://localhost:8080에서 어플리케이션을 확인해요. 펫 클리닉 어플리케이션을 볼 수 있어요.
터미널에서 다음 명령을 실행해 어플리케이션을 중지해요.
$ docker compose down
Compose 명령에 대한 자세한 내용은 Compose CLI reference를 참고해요.
Java 개발에 컨테이너 사용하기
준비 사항 (Prerequisites)
어플리케이션 컨테이너화 단계를 먼저 완료해요.
개요 (Overview)
이 섹션에서는 앞서 컨테이너화한 어플리케이션을 위한 로컬 개발 환경을 구성하는 과정을 살펴봐요. 여기에는 다음이 포함돼요:
- 로컬 데이터베이스를 추가하고 데이터를 유지(Persist)하기
- 디버거를 연결할 개발용 컨테이너 만들기
- 코드를 수정·저장할 때 실행 중인 Compose 서비스가 자동으로 갱신되도록 Compose 구성하기
로컬 데이터베이스 추가 및 데이터 유지
컨테이너를 사용해 데이터베이스 같은 로컬 서비스를 구성할 수 있어요. 이 섹션에서는 docker-compose.yaml 파일을 수정해 데이터베이스 서비스와 데이터를 유지할 볼륨(volume)을 정의할 거예요. 그리고 이 어플리케이션은 데이터베이스 타입을 정의하는 데 시스템 속성(system property)을 사용하므로, 앱 실행 시 이 시스템 속성을 넘겨주도록 Dockerfile도 수정해야 해요.
클론한 저장소 디렉토리에서 docker-compose.yaml 파일을 IDE나 텍스트 편집기로 열어요. Compose 파일에 예시 데이터베이스 서비스가 있지만, 여러분만의 어플리케이션에 맞게 몇 가지를 바꿔야 해요.
docker-compose.yaml 파일에서 다음을 수행해야 해요:
- 데이터베이스 지시사항을 전부 주석 해제해요. 이제 데이터를 로컬 저장소 대신 데이터베이스 서비스를 사용하게 돼요.
- 최상위의
secrets요소와db서비스 안의 요소를 제거해요. 이 예제에서는 secrets 대신 환경 변수로 비밀번호를 사용해요. db서비스에서user요소를 제거해요. 이 예제에서는 환경 변수에서 user를 지정해요.- 데이터베이스 환경 변수를 업데이트해요. 이것들은 Postgres 이미지에서 정의된 값이에요. 자세한 내용은 Postgres Official Docker Image를 참고해요.
db서비스의 healthcheck 테스트를 업데이트하고 user를 지정해요. 기본적으로 healthcheck는 정의한petclinicuser가 아닌 root user를 사용해요.server서비스에 데이터베이스 URL을 환경 변수로 추가해요. 이것은spring-petclinic/src/main/resources/application-postgres.properties에 정의된 기본값을 덮어써요.
다음은 업데이트된 docker-compose.yaml 파일이에요. 주석은 모두 제거됐어요.
services:
server:
build:
context: .
ports:
- 8080:8080
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
db:
image: postgres:18
restart: always
volumes:
- db-data:/var/lib/postgresql
environment:
- POSTGRES_DB=petclinic
- POSTGRES_USER=petclinic
- POSTGRES_PASSWORD=petclinic
ports:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "petclinic"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
Dockerfile을 IDE나 텍스트 편집기로 열어요. ENTRYPOINT 지시문에서 spring-petclinic/src/resources/db/postgres/petclinic_db_setup_postgres.txt 파일에 지정된 대로 시스템 속성을 넘겨주도록 지시문을 수정해요.
- ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]
+ ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
모든 파일을 저장하고 닫아요.
이제 다음 docker compose up 명령을 실행해 어플리케이션을 시작해요.
$ docker compose up --build
브라우저를 열고 http://localhost:8080에서 어플리케이션을 확인해요. 펫 클리닉 어플리케이션을 볼 수 있어요. 어플리케이션을 살펴보며 Veterinarians로 이동해 수의사 목록을 불러와서 어플리케이션이 데이터베이스에 연결됐는지 확인해요.
터미널에서 ctrl+c를 눌러 어플리케이션을 중지해요.
개발용 Dockerfile
지금의 Dockerfile은 어플리케이션을 실행하는 데 필요한 구성 요소만 담은 작고 안전한 프로덕션 이미지를 만들기에 좋아요. 개발할 때는 환경이 다른 이미지가 필요할 수 있어요.
예를 들어 개발용 이미지에서는 실행 중인 Java 프로세스에 디버거를 연결할 수 있도록 어플리케이션을 시작하도록 설정하고 싶을 거예요.
여러 개의 Dockerfile을 관리하는 대신 새 스테이지를 추가할 수 있어요. 그러면 Dockerfile이 프로덕션 준비가 된 final 이미지와 개발용 이미지를 모두 만들 수 있어요.
Dockerfile의 내용을 다음과 같이 교체해요.
# syntax=docker/dockerfile:1
FROM eclipse-temurin:21-jdk-jammy as deps
WORKDIR /build
COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests
FROM deps as package
WORKDIR /build
COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
FROM package as extract
WORKDIR /build
RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted
FROM extract as development
WORKDIR /build
RUN cp -r /build/target/extracted/dependencies/. ./
RUN cp -r /build/target/extracted/spring-boot-loader/. ./
RUN cp -r /build/target/extracted/snapshot-dependencies/. ./
RUN cp -r /build/target/extracted/application/. ./
ENV JAVA_TOOL_OPTIONS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
CMD [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
FROM eclipse-temurin:21-jre-jammy AS final
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./
EXPOSE 8080
ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
Dockerfile을 저장하고 닫아요.
Dockerfile에 extract 스테이지를 기반으로 하는 development라는 새 스테이지를 추가했어요. 이 스테이지에서는 추출된 파일을 공통 디렉토리에 복사한 다음, 어플리케이션을 시작하는 명령을 실행해요. 이 명령에서는 8000 포트를 노출하고 JVM용 디버그 구성을 선언해서 디버거를 연결할 수 있게 해요.
로컬 개발에 Compose 사용하기
현재 Compose 파일은 개발 컨테이너를 시작하지 않아요. 그러려면 Compose 파일을 개발 스테이지를 대상으로 하도록 수정해야 해요. 그리고 디버거에 접근할 수 있도록 server 서비스의 포트 매핑도 업데이트해요.
docker-compose.yaml을 열고 다음 지시사항을 파일에 추가해요.
services:
server:
build:
context: .
target: development
ports:
- 8080:8080
- 8000:8000
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
db:
image: postgres:18
restart: always
volumes:
- db-data:/var/lib/postgresql
environment:
- POSTGRES_DB=petclinic
- POSTGRES_USER=petclinic
- POSTGRES_PASSWORD=petclinic
ports:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "petclinic"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
이제 어플리케이션을 시작하고 실행 중인지 확인해요.
$ docker compose up --build
마지막으로 API 엔드포인트를 테스트해요. 다음 curl 명령을 실행해요:
$ curl --request GET \
--url http://localhost:8080/vets \
--header 'content-type: application/json'
다음과 같은 응답을 받게 돼요:
{
"vetList": [
{
"id": 1,
"firstName": "James",
"lastName": "Carter",
"specialties": [],
"nrOfSpecialties": 0,
"new": false
},
{
"id": 2,
"firstName": "Helen",
"lastName": "Leary",
"specialties": [{ "id": 1, "name": "radiology", "new": false }],
"nrOfSpecialties": 1,
"new": false
},
{
"id": 3,
"firstName": "Linda",
"lastName": "Douglas",
"specialties": [
{ "id": 3, "name": "dentistry", "new": false },
{ "id": 2, "name": "surgery", "new": false }
],
"nrOfSpecialties": 2,
"new": false
},
{
"id": 4,
"firstName": "Rafael",
"lastName": "Ortega",
"specialties": [{ "id": 2, "name": "surgery", "new": false }],
"nrOfSpecialties": 1,
"new": false
},
{
"id": 5,
"firstName": "Henry",
"lastName": "Stevens",
"specialties": [{ "id": 1, "name": "radiology", "new": false }],
"nrOfSpecialties": 1,
"new": false
},
{
"id": 6,
"firstName": "Sharon",
"lastName": "Jenkins",
"specialties": [],
"nrOfSpecialties": 0,
"new": false
}
]
}
디버거 연결하기
IntelliJ IDEA에 내장된 디버거를 사용할 거예요. 이 IDE의 커뮤니티 버전도 사용할 수 있어요. IntelliJ IDEA에서 프로젝트를 열고 Run 메뉴로 이동한 다음 Edit Configuration을 선택해요. 다음과 유사하게 새 Remote JVM Debug 구성을 추가해요.

중단점(breakpoint)을 설정해요.
src/main/java/org/springframework/samples/petclinic/vet/VetController.java를 열고 showResourcesVetList 함수 안에 중단점을 추가해요.
디버그 세션을 시작하려면 Run 메뉴에서 **Debug NameOfYourConfiguration**을 선택해요.

이제 Compose 어플리케이션의 로그에서 연결을 볼 수 있어요.

이제 서버 엔드포인트를 호출할 수 있어요.
$ curl --request GET --url http://localhost:8080/vets
코드가 표시된 줄에서 중단됐을 거예요. 이제 평소처럼 디버거를 사용할 수 있어요. 변수를 검사·감시하거나, 조건부 중단점을 설정하고, 스택 트레이스를 보는 등 다양한 작업을 할 수 있어요.

터미널에서 ctrl+c를 눌러 어플리케이션을 중지해요.
서비스 자동 업데이트
Compose Watch를 사용하면 코드를 수정·저장할 때 실행 중인 Compose 서비스를 자동으로 갱신할 수 있어요. Compose Watch에 대한 자세한 내용은 Use Compose Watch를 참고해요.
docker-compose.yaml 파일을 IDE나 텍스트 편집기로 연 다음 Compose Watch 지시사항을 추가해요. 다음은 업데이트된 docker-compose.yaml 파일이에요.
services:
server:
build:
context: .
target: development
ports:
- 8080:8080
- 8000:8000
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
develop:
watch:
- action: rebuild
path: .
db:
image: postgres:18
restart: always
volumes:
- db-data:/var/lib/postgresql
environment:
- POSTGRES_DB=petclinic
- POSTGRES_USER=petclinic
- POSTGRES_PASSWORD=petclinic
ports:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "petclinic"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
다음 명령을 실행해 Compose Watch로 어플리케이션을 실행해요.
$ docker compose watch
웹 브라우저를 열고 http://localhost:8080에서 어플리케이션을 확인해요. Spring Pet Clinic 홈페이지를 볼 수 있어요.
로컬 머신의 어플리케이션 소스 파일에 어떤 변경이든, 이제 실행 중인 컨테이너에 자동으로 반영돼요.
spring-petclinic/src/main/resources/templates/fragments/layout.html을 IDE나 텍스트 편집기로 열고 Home 내비게이션 문자열에 느낌표를 추가해 수정해요.
- <li th:replace="~{::menuItem ('/','home','home page','home','Home')}">
+ <li th:replace="~{::menuItem ('/','home','home page','home','Home!')}">
layout.html의 변경 사항을 저장하면 컨테이너가 자동으로 다시 빌드되는 동안 개발을 계속할 수 있어요.
컨테이너가 다시 빌드되어 실행된 후 http://localhost:8080을 새로고침해서 메뉴에 Home! 이 나타나는지 확인해요.
터미널에서 ctrl+c를 눌러 Compose Watch를 중지해요.
Java 테스트 실행하기
준비 사항 (Prerequisites)
이 가이드의 이전 섹션을 전부 완료해요 — Java 어플리케이션 컨테이너화부터 시작해서요.
개요 (Overview)
테스트는 현대 소프트웨어 개발의 필수적인 부분이에요. 테스트는 개발 팀마다 서로 다른 의미를 가질 수 있어요. 단위 테스트, 통합 테스트, end-to-end 테스트가 있죠. 이 가이드에서는 Docker에서 단위 테스트를 실행하는 방법을 살펴봐요.
테스트용 다단계(Multi-stage) Dockerfile
다음 예제에서는 테스트 명령을 Dockerfile로 가져올 거예요. Dockerfile의 내용을 다음과 같이 교체해요.
# syntax=docker/dockerfile:1
FROM eclipse-temurin:21-jdk-jammy as base
WORKDIR /build
COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/
FROM base as test
WORKDIR /build
COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw test
FROM base as deps
WORKDIR /build
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw dependency:go-offline -DskipTests
FROM deps as package
WORKDIR /build
COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
FROM package as extract
WORKDIR /build
RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted
FROM extract as development
WORKDIR /build
RUN cp -r /build/target/extracted/dependencies/. ./
RUN cp -r /build/target/extracted/spring-boot-loader/. ./
RUN cp -r /build/target/extracted/snapshot-dependencies/. ./
RUN cp -r /build/target/extracted/application/. ./
ENV JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"
CMD [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
FROM eclipse-temurin:21-jre-jammy AS final
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./
EXPOSE 8080
ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
먼저 새 base 스테이지를 추가했어요. base 스테이지에는 test와 deps 스테이지 양쪽 모두가 필요로 하는 공통 지시사항을 넣었어요.
다음으로 base 스테이지를 기반으로 하는 test라는 새 테스트 스테이지를 추가했어요. 이 스테이지에서는 필요한 소스 파일을 복사한 다음 RUN으로 ./mvnw test를 실행하도록 지정했어요. CMD 대신 RUN을 사용해 테스트를 실행했어요. 그 이유는 CMD 지시문은 컨테이너가 실행될 때 실행되고, RUN 지시문은 이미지가 빌드되는 동안 실행되기 때문이에요. RUN을 사용하면 테스트가 실패할 경우 빌드도 실패해요.
마지막으로 deps 스테이지를 base 스테이지 기반으로 업데이트하고, 이제 base 스테이지에 들어간 지시사항은 제거했어요.
test 스테이지를 대상으로 지정해 새 이미지를 빌드하고 테스트 결과를 확인하려면 다음 명령을 실행해요. 빌드 출력을 보려면 --progress=plain을, 테스트가 항상 실행되도록 하려면 --no-cache를, test 스테이지를 대상으로 하려면 --target test를 포함하세요.
이제 이미지를 빌드하고 테스트를 실행해요. docker build 명령에 --target test 플래그를 추가해 테스트 빌드 스테이지를 구체적으로 실행할 거예요.
$ docker build -t java-docker-image-test --progress=plain --no-cache --target=test .
다음과 같은 내용의 출력을 볼 수 있어요.
...
#15 101.3 [WARNING] Tests run: 45, Failures: 0, Errors: 0, Skipped: 2
#15 101.3 [INFO]
#15 101.3 [INFO] ------------------------------------------------------------------------
#15 101.3 [INFO] BUILD SUCCESS
#15 101.3 [INFO] ------------------------------------------------------------------------
#15 101.3 [INFO] Total time: 01:39 min
#15 101.3 [INFO] Finished at: 2024-02-01T23:24:48Z
#15 101.3 [INFO] ------------------------------------------------------------------------
#15 DONE 101.4s