Creating spring boot multi-stage build with docker

Creating spring boot multi-stage build with docker

How to create a Spring Boot multi-stage build with Docker

In this tutorial, we are going to learn how to build and run our Spring Boot application using Docker. First, we will pre-build our application to generate the fat jar file, and Docker will later utilize it for deployment. Later, we will deep dive into a multi-stage build.
Before we move forward, you can follow some of our other tutorials.

Adding Spring profile for Docker

We are deploying the application with Docker, so first, we will configure the Docker profile for different configurations.
Inside application.yml file
java
server.port: 8001

logging:
  level:
    root: INFO
    com.csbyte: DEBUG

---
spring.config.activate.on-profile: docker
server.port: 8080
Here, we are separating the local environment to docker environment by dashes(---). And set the profile to docker.
This is a simple configuration that only has a different port for the docker profile.

Build The Changes

Move to the project directory and build the project
bash
./gradlew build
It will create the fat jar file inside build/libs

Build the Docker image

Create Docker File

Let's create a Dockerfile inside the root directory in your project.
Note: the Docker file must not contain any extension.
bash
FROM eclipse-temurin:17-jdk-alpine

EXPOSE 8080

ADD ./build/libs/*.jar app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]
Here, we are using JDK 17 if you want the latest version of Java used accordingly. The setup for the JDK 25 will be as follows.
bash
FROM eclipse-temurin:25-jre-alpine

EXPOSE 8080

ADD ./build/libs/*.jar app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]
  • The Docker image for JDK 17 is used
  • Port 8080 is exposed to the other Docker container or the internet.
  • The fat Jar file will be added from the directory build/libs that we built previously.
  • ENTRYPOINT command is used by Docker to start the container based on the Docker image i.e java -jar /app.jar
Here are some disadvantages of using these methods.
  • When the Docker container starts up, the fat JAR will take time to unpack.
  • Since the fat JAR is big, if we want to deploy repeated changes to the code, running the Docker deployment layer is not optimal.
This leads to the multi-stage build.

Multi-Stage Build

Let's create the multi-stage build Docker file.
bash
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR extracted
ADD ./build/libs/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:17-jdk-alpine
WORKDIR app
COPY --from=builder extracted/dependencies/ ./
COPY --from=builder extracted/spring-boot-loader/ ./
COPY --from=builder extracted/snapshot-dependencies/ ./
COPY --from=builder extracted/application/ ./

EXPOSE 8080

ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
This makes it possible to extract the fat JAR content into a number of folders.
  • FROM eclipse-temurin:17-jdk-alpine AS builder it will pull the JDK image from Temurin and named as builder.
  • We have created the working directory as extracted and added the fat JAR file to this directory.
  • RUN java -Djarmode=layertools -jar app.jar extract the build stage, run this command to perform the extraction of the fat JAR file into its working directory.
  • FROM eclipse-temurin:17-jdk-alpine it will use the same Docker image and copy the files from the builder stage folders.
  • --from=builder extracted/dependencies/ third-party libraries like Hibernate, which change less frequently. So they are copied first. Docker cache this layer
  • --from=builder extracted/spring-boot-loader/ handling launching the application. This contains the classes that are responsible for launching the application.
  • --from=builder extracted/snapshot-dependencies/ contains snapshot dependencies, if any.
  • --from=builder extracted/application/ our Spring Boot classes like controller, services, configuration classes. Changes in the app code will invalidate and build other heavy-lifting dependencies that are not required to build each time.
  • loader.launch.JarLauncher Instead of java -jar app.jar (which forces Java to uncompress files at startup), it directly invokes Spring Boot's JarLauncher. Resulting in faster container startup times and slightly lower memory consumption

Build Image

bash
docker build -t product-service .
Make sure to use your own app instead of product-service .
The output looks something like this.
bash
[+] Building 0.6s (14/14) FINISHED                                                                                                                                                                                             docker:default
 => [internal] load build definition from Dockerfile                                                                                                                                                                                     0.0s
 => => transferring dockerfile: 522B                                                                                                                                                                                                     0.0s
 => [internal] load metadata for docker.io/library/eclipse-temurin:17-jdk-alpine                                                                                                                                                         0.4s
 => [internal] load .dockerignore                                                                                                                                                                                                        0.0s
 => => transferring context: 2B   

Run the Docker image

bash
docker run -d --name product-service -p8080:8080 -e SPRING_PROFILES_ACTIVE=docker product-service
This will run the application. You can test the endpoints of your application.
In this tutorial, we learn how to set up the multi-stage build for a Spring Boot application. By doing so, we are able to create a high-performance, faster container startup application deployment.