Dockerizing a Java 24 Project with Docker Init

Hello Wowlrd!

I'm starting a new series called "Docker Hour", where I'll publish a new Docker-related article every week. But in the beginning, I'll publish a new article for two weeks, so 10 articles in total. I wanted to called the series "Docker Deca", but I'm just making up too many names.

Later, I'll give a talk at PlatformCon named:

Bake a Docker Cake
10 Docker Commands You Probably Didn't Know About

It's going to be about these 10 commands. So, let's init!

Docker Init

Docker Init was a new command introduced in Docker Desktop 4.27 (for comparison, currently, the latest version is 4.40). It's a "smart" command that helps you Dockerize your project. It does the following:

  • Creates a Dockerfile
  • Creates a docker-compose.yml file (well, they call it compose.yaml to be more of a cool guy)
  • Creates a .dockerignore file
  • Creates a README file with instructions on how to run the project

It's great stuff! Let's see it in action.

Technical Requirements

  • Docker Desktop 4.27 or later
  • Git is good to have, I guess

Create a New Project, or Rather Download One

I'm going to do it with a Spring Boot project. Because it's in early Spring now, and I haven't done a Spring Boot project in a while. So, let's do it!

Let's go to start.spring.io because Josh Long loves this site. And let's create a new project with the following settings:

  • Project: Maven
  • Language: Java
  • Spring Boot: 3.4.4
  • Packaging: Jar
  • Java: 24

I also named added the following metadata, but you can choose your own:

  • Group: com.dockerhour
  • Artifact: docker-init
  • Name: docker-init
  • Description: Docker Init
  • Package Name: com.dockerhour.dockerinit

Then, I downloaded the project and unzipped it. I also renamed the folder to docker-init. Now, let's go to the folder:

cd docker-init

That was easy enough! Now, it's time!

Run Docker Init

Now, let's run the command:

docker init

The interactive wizard will detect that you have Java project. Press Enter to accept the "Java" option, accept the default for source directory and Java version, and enter the port manually:

? What application platform does your project use? Java
? What's the relative directory (with a leading .) for your app? ./src
? What version of Java do you want to use? 24
? What port does your server listen on? 8080

The following files are generated:

  • Dockerfile: The Dockerfile to build the image.
  • compose.yaml: The Docker Compose file to run the image.
  • .dockerignore: The .dockerignore file to exclude files from the build context.
  • README.Docker.md: The README file with instructions on how to build and run the image.

Let's take a look into the Dockerfile:

# syntax=docker/dockerfile:1

################################################################################

FROM eclipse-temurin:24-jdk-jammy as deps

WORKDIR /build

COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/

RUN --mount=type=bind,source=pom.xml,target=pom.xml \
    --mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests

################################################################################

FROM deps as package

WORKDIR /build

COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
    --mount=type=cache,target=/root/.m2 \
    ./mvnw package -DskipTests && \
    mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar

################################################################################

FROM package as extract

WORKDIR /build

RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted

################################################################################

FROM eclipse-temurin:24-jre-jammy AS final

ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser
USER appuser

COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./

EXPOSE 8080

ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]

The following base images are used:

  • eclipse-temurin:24-jdk-jammy: The Java Development Kit (JDK) image to compile the Java code, based on Ubuntu 22.04 (Jammy Jellyfish).
  • eclipse-temurin:24-jre-jammy: The Java Runtime Environment (JRE) image to run the compiled Java code, based on Ubuntu 22.04 (Jammy Jellyfish).

At the time of writing this article, Java 24 was recently released, so the Eclipse Temurin images are not available yet. To address that, we will use the following images instead:

  • sapmachine:24-jdk-ubuntu-noble: The JDK image based on Ubuntu 24.04 (Noble Numbat).
  • sapmachine:24-jre-ubuntu-noble: The JRE image based on Ubuntu 24.04 (Noble Numbat).

These images are provided by SAP, the German vendor of ERP systems, who also provides a free and open-source distribution of the OpenJDK. You can find their Java images on Docker Hub: hub.docker.com/_/sapmachine.

After replacing the base images in the Dockerfile, you can execute the following command to build and run the image:

docker compose up

This command will use the Docker Compose configuration that looks like this:

services:
  server:
    build:
      context: .
    ports:
      - 8080:8080

The server service will build the image using the Dockerfile in the current directory and expose the port 8080 on the host machine.

The application starts successfully, but also directly stops with exit code 0. This means that the application is running, but there is no endpoint to access it.

Let's add a simple endpoint to the application.

Add a Controller

Let's add a simple controller to the application. Create a new file src/main/java/come/dockerhour/dockerinit/hello/HelloController.java with the following content:

package com.dockerhour.dockerinit.hello;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/")
    public String hello() {
        return "Hello, Docker Hour!";
    }
}

Also, add the following block to your pom.xml to include the Spring Web dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Now, let's build the project and run it again:

docker compose up --build

The application should start successfully and you can access it at localhost:8080. Let's check the endpoint:

curl http://localhost:8080

It should say "Hello, Docker Security!". Voilà!

If you want to test Docker Init with other languages, you can check the guides on Docker's website: docs.docker.com/guides. E.g. you can check the C++ guide, especially because I co-authored it:

The guide starts like this:

Docker would like to thank Pradumna Saraf and Mohammad-Ali A'râbi for their contribution to this guide.

So, I was the one writing these words, so I thanked myself on behalf of Docker. I hope you like it!

Conclusion

The new Java just came out, and Docker Init is a great tool to help you Dockerize your Java project. It creates a Dockerfile, a Docker Compose file, a .dockerignore file, and a README file with instructions on how to run the project.

Docker Init for Java only works with Maven projects for now. But you can use the files as a starting point and adapt them to your project. This is what we did here, because the Eclipse Temurin images were not available yet.

I hope you liked the article! Please don't forget to like and subscribe to my channel.

If you want to learn about Docker Security basics, grab the first chapter of my book for free: Docker and Kubernetes Security. It's a great book, and I wrote it myself. So, you can trust me!

Other Posts in the Series

Post thumbnail
A guide to determine the Git branch you're on using various methods.
Issue: #1
Series: Git Weekly
Beginner
Published: 3/28/2022
Post thumbnail
A guide to change a commit's author on Git, including changing the last commit's author and an older commit's author.
Issue: #2
Series: Git Weekly
Intermediate
Published: 4/4/2022
Post thumbnail
A guide to understanding the differences between Git merge and rebase, including fast-forward merge, real merge, and squash merge.
Issue: #3
Series: Git Weekly
Intermediate
Published: 4/11/2022