Shorts

How to dockerize a Spring Boot app with the smallest base image

Mar 9, 2023
Dmitry Chuyko
8.6

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!

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 --from=builder /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 --from=builder /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 --from=optimizer /home/app/dependencies/ ./
COPY --from=optimizer /home/app/spring-boot-loader/ ./
COPY --from=optimizer /home/app/snapshot-dependencies/ ./
COPY --from=optimizer /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.

 

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading