Dockerize a Spring Boot application
Introduction
In this post, I’d like to present a few options to ship a spring boot application in a docker container. There are many ways to dockerize a spring boot (probably a nice google hit search), but I don’t see too much discussion around the pros and cons. So let’s jump into it.
Create new project
Go to https://start.spring.io/ and create a new project. I’ll be using:
- Gradle - Groovy
- Spring Boot 3.4.2
- Java 21
- Dependencies: Spring Web
For demonstration, I’m going to add a “/ping” endpoint and it’s going to return “pong”. Just simply create PingController.java
.
package com.nukesz.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PingController {
@GetMapping("/ping")
public String ping() {
return "Pong";
}
}
Build and run the application as a normal jar:
# Build it
./gradlew build
# Run it
java -jar build/libs/dockerize-spring-boot-0.0.1-SNAPSHOT.jar
The jar is actually created with the
bootJar
task. You can learn more about it in the spring doc
Verify our REST API is working as expected:
$ curl http://localhost:8080/ping
> Pong!
Create Dockerfile
manually
Our application is ready, so let’s create a docker image for it. First let’s
FROM eclipse-temurin:21
LABEL org.opencontainers.image.authors="Norbert Benczur"
RUN mkdir /opt/app
COPY build/libs/dockerize-spring-boot-*.jar /opt/app/myapp.jar
CMD ["java", "-jar", "/opt/app/myapp.jar"]
You can build and run the Docker image:
docker build -t dockerize-spring-boot .
docker run -it -p 8080:8080 --rm dockerize-spring-boot
Verify that we can reach our REST API within the container as expected:
$ curl http://localhost:8080/ping
> Pong!
Are we done? - Not at all.
What’s the problem?
Creating Dockerfile
manually has its pros and cons. It’s the most flexible solution where you control everything. No dependency needed.
But I think the biggest drawback with this approach is that it seems everything is working, but in fact it is hiding the underlining work that is missing. The problem comes when you need more than a Hello World
example.
Repetitive
When you have more than 1 java app to dockerize, the number of dockerfiles starts to grow and you have to maintain and update each file independently.
Efficiency
In this simple example, we defined our base image and started our fat jar. But is that the most optimal way to build and run a spring boot (or any other java) application? For example, let’s change a single file in our application and build the image again:
# Let's measure the re-build
$ time ( ./gradlew build -x test; docker build -t dockerize-spring-boot . )
> ..
> => [3/3] COPY build/libs/dockerize-spring-boot-*.jar /opt/app/myapp.jar
> ..
> real 0m7,640s
So even a single change could cause the jar to be re-built and copied again. We are obviously not using the benefits of docker layers.
Can’t we leverage other people’s work rather than trying to come up with most optimal Dockerfile
ourself?
Use Buildpack
TBD