In one of our previous articles, we discussed buildpacks and how they accelerate development. In this article, we will show two approaches to dockerizing a Spring Boot application: one uses a Dockerfile, and another leverages a buildpack.
By the way, another way to optimize Spring Boot footprint in the cloud is to use Java with CRaC support: Alpaquita Containers with CRaC help to create 10% smaller images!
Table of Contents
Prerequisites:
- Docker
- Your favorite IDE
Containerize Spring Boot with Dockerfile
Build a Spring Boot app
First, let us write a basic Spring Boot application to have something to work with. Go to Spring Initializr. Select your preferred build system (Maven or Gradle), Java, JAR, Java version 21. Then, select the Spring Web dependency. Download the project and unzip it.
Open the project in your favorite IDE and create a HomeController
class configured in the following way:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping
public String sayHello() {
return "Hello Docker!";
}
}
Write a Dockerfile
We will use a Liberica Runtime Container, which includes a lightweight musl-based Alpaquita Linux and Liberica Lite optimized for Cloud deployment. Liberica JDK Lite is a flavor of Liberica JDK, a Java runtime recommended by Spring.
You will need a following Dockerfile:
FROM bellsoft/liberica-runtime-container:jdk-21-stream-musl as builder
WORKDIR /app
ADD demo /app/demo
RUN cd demo && ./mvnw package
FROM bellsoft/liberica-runtime-container:jre-21-musl
WORKDIR /app
EXPOSE 8080
CMD ["java", "-jar", "/app/demo.jar"]
COPY /app/demo/target/*.jar /app/demo.jar
Here, we build a project inside a Docker image based on Liberica JDK and then copy it into a fresh base image with Liberica JRE, where we can run the application. You need the EXPOSE 8080 line if you have a web application.
This Dockerfile enables us to create and run an executable JAR of our Spring Boot app. But you can go even further and use a layered JAR. In this case, the application classes and dependencies are stored in different layers. The layers that are more frequently updated are placed on the top, and other layers can be pulled from the cache, making image updates faster.
To build a layered JAR, you need to use the -Djarmode=tools
property. When running the resulting image, you will need a special org.springframework.boot.loader.launch.JarLauncher
class that knows how to work with layered JARs. The resulting Dockerfile is as follows:
FROM bellsoft/liberica-runtime-container:jdk-21-stream-musl as builder
WORKDIR /app
ADD demo /app/demo
RUN cd demo && ./mvnw package
FROM bellsoft/liberica-runtime-container:jdk-21-cds-slim-musl as optimizer
WORKDIR /app
COPY /app/spring-petclinic-main/target/*.jar petclinic.jar
RUN java -Djarmode=tools -jar petclinic.jar extract --layers --launcher
FROM bellsoft/liberica-runtime-container:jre-21-stream-musl
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
EXPOSE 8080
COPY /home/app/dependencies/ ./
COPY /home/app/spring-boot-loader/ ./
COPY /home/app/snapshot-dependencies/ ./
COPY /home/app/application/ ./
Build a Docker container image
Run
docker build . -t demo-boot-docker
Check the image size:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-boot-docker latest e67abffec5d3 9 seconds ago 159MB
The resulting image based on JRE and a lightweight Linux distro will help you reduce memory footprint in the cloud.
But what if reducing the startup time of your Java application in a container is also critical? In this case, you can take advantage of Application Class Data Sharing (AppCDS). There’s no need to adjust the code of your app, only a couple of lines in your Dockerfile. For more details, refer to our guide Using CDS with Spring Boot.
Run the Spring Boot Docker image
To run the Docker image from the command line, use the following command:
docker run --rm -p 8080:8080 demo-boot-docker
If you visit localhost:8080, you will see our “Hello Docker!” message.
Containerize Spring Boot with Paketo buildpack
Let’s take the same project we developed in the previous section. Spring Boot supports building Docker images with buildpacks out-of-the-box, using Maven or Gradle plugin. If you don’t need to configure the image additionally, you can simply run one command to containerize your app without writing a Dockerfile.
BellSoft Liberica JDK is the default JVM with Paketo buildpacks. It provides a JDK at build time and JRE at runtime, so the resulting container will be smaller.
To build an image from the source with Maven, run
mvn spring-boot:build-image
If you use Gradle, use the following command to build a Docker image:
gradle bootBuildImage
Check the images with
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-boot-docker 0.0.1-SNAPSHOT 372afdfa3ea0 1 minute ago 352MB
As you can see, the resulting image is two times bigger than the one we built using a Dockerfile, the root cause being the underlying operating system: the official Spring Boot buildpacks use Ubuntu.
But we can still benefit from buildpacks and get a smaller resulting image if we take BellSoft’s buildpacks based on Alpaquita. To do that, you need to add the following configuration to the Maven plugin in pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>demo-bellsoft-buildpack</name>
<builder>bellsoft/buildpacks.builder:musl</builder>
</image>
</configuration>
</plugin>
</plugins>
</build>
If you use Gradle, configure the plugin in the following way:
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.6'
id 'io.spring.dependency-management' version '1.1.0'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
bootBuildImage {
imageName = "demo-bellsoft-buildpack"
builder = "bellsoft/buildpacks.builder:musl"
}
The command for building an image doesn’t change. For Maven:
mvn spring-boot:build-image
For Gradle:
gradle bootBuildImage
Check the images:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-bellsoft-buildpack latest 0f5f091304d8 1 minute ago 140MB
The resulting image is just as small as the one we build with a Dockerfile!
Java container size comparison
We now have three containers with the same Java application, and the size difference is striking. Our containers based on Alpaquita and Liberica JRE Lite are 159MB and 140MB, which is more than twice as small as the container we built with Liberica JRE and Ubuntu via a buildpack.
Alpaquita + Liberica Lite |
Liberica JRE + Ubuntu via the Paketo buildpack |
Liberica JRE + Alpaquita via the Paketo buildpack |
159MB |
352MB |
140MB |
Java container size comparison
Conclusion
As you can see, switching to a lightweight base OS image makes a drastic difference regarding container size. But migration to Alpaquita gives you additional benefits:
- Two libc implementations available, optimized musl and glibc;
- Enhanced performance compared to other popular Linux distributions;
- The only Linux optimized for Java development;
- LTS versions as part of commercial support;
- 100% compatibility with Liberica JDK and Liberica NIK, products used and recommended by the Spring team.
Want to know more about Alpaquita? Head to Alpaquita Documentation for install guides, feature overview, and tuning recommendations.