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

Avatar
Norbert Benczúr
Senior Developer / R&D Manager
comments powered by Disqus