background image

Comparing Various Docker Images for Quarkus Applications

by pp
Updated:

Let's continue with source code from article First Hello World Quarkus. Quarkus CLI tool already created 4 dockerfiles:

$ tree src/main/docker/
src/main/docker/
├── Dockerfile.jvm
├── Dockerfile.legacy-jar
├── Dockerfile.native
└── Dockerfile.native-micro

0 directories, 4 files

Let's explain what these are for.

  • Dockerfile.jvm: This Dockerfile is used to build a Docker image for running your Quarkus application in JVM mode. It includes a Java runtime environment and runs your application as a standard Java application.
  • Dockerfile.legacy-jar: This Dockerfile is similar to Dockerfile.jvm, but it’s used for building a Docker image for running a Quarkus application packaged as a legacy JAR (also known as an über JAR or fat JAR). This type of JAR includes all of the application’s dependencies.
  • Dockerfile.native: This Dockerfile is used to build a Docker image for running your Quarkus application in native mode. It doesn’t include a Java runtime environment because the application is compiled to a native executable. As a result, the Docker image is smaller and the application starts faster, but the build process is longer and requires more memory.
  • Dockerfile.native-micro: This Dockerfile is similar to Dockerfile.native, but it’s optimized for creating a minimal Docker image. It uses Docker multi-stage builds to separate the build stage and the runtime stage, resulting in a smaller Docker image.

Let's build each of them:

Dockerfile.jvm

To build the Docker image for your Quarkus application, follow these steps. Make sure you have either Docker or Podman installed on your machine. The commands are interchangeable, allowing you to use either Docker or Podman based on your setup.

Navigate to your project directory and execute the following command to build the application and create the Docker image:

./mvnw package
docker build -f src/main/docker/Dockerfile.jvm -t quarkus/hello-world .

or with Podman:

./mvnw package
podman build -f src/main/docker/Dockerfile.jvm -t quarkus/hello-world .

This command compiles your Quarkus application using Maven (./mvnw package) and then builds a Docker image named quarkus/hello-world based on the specified Dockerfile (-f src/main/docker/Dockerfile.jvm).

Once the image is built, you can run your Quarkus application in a Docker container using the following command:

docker run -it --rm -p 8080:8080 quarkus/hello-world

This command starts a Docker container in interactive mode (-it), removes the container automatically when it stops (--rm), and maps port 8080 from the container to the host machine (-p 8080:8080). Adjust the port mapping if needed, depending on your application's configuration.

Now, you have successfully built and run your Quarkus application in a Docker container. Feel free to access it through your web browser at http://localhost:8080 or modify the port mapping accordingly.

Dockerfile.legacy-jar

Build the image

./mvnw package -Dquarkus.package.type=legacy-jar
docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/hello-world-legacy .

and run it with

docker run -it --rm -p 8080:8080 quarkus/hello-world-legacy

Dockerfile.native

Build the native executable

./mvnw package -Dnative

Check that executable is running outside of container

./target/hello-world-1.0.0-SNAPSHOT-runner

Build the native image

docker build -f src/main/docker/Dockerfile.native -t quarkus/hello-world-native .

and run the image

docker run -it --rm -p 8080:8080 quarkus/hello-world-native

Dockerfile.native-micro

Build the micro native image

./mvnw package -Dnative
docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/hello-world-native-micro .

and run it with

docker run -it --rm -p 8080:8080 quarkus/hello-world-native-micro

Compare created images

Now, when we created all docker images, we can compare their pros and cons. First, let's have a look at the image size:

$ docker images |grep quarkus
quarkus/hello-world-native-micro   latest    002b7d38de54   About a minute ago   85.5MB
quarkus/hello-world-native         latest    73c012f38abc   3 minutes ago        150MB
quarkus/hello-world-legacy         latest    be07e334f8a2   About an hour ago    466MB
quarkus/hello-world                latest    ce373e98f3af   About an hour ago    466MB

Once we determine the size of the container images, we use a script to measure the time it takes to create the image and test its startup speed. Subsequently, we compile this information into a table:

Image name Dockerfile type Size (MB) Build time (s) Startup time (s) Base image
hello-world jvm 466 22 0.949 ubi8/openjdk-17:1.18
hello-world-legacy legacy-jar 466 18 1.095 ubi8/openjdk-17:1.18
hello-world-native native 150 265 0.015 ubi8/ubi-minimal:8.9
hello-world-native-micro native-micro 86 280 0.012 quarkus/quarkus-micro-image:2.0

Times were measured on Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz.

The table provides an overview of the image names, Dockerfile types, sizes, build times, startup times, and base images used for each Quarkus application mode. It can be helpful for choosing the appropriate image based on specific requirements such as image size and startup time. Non-native images are useful for development or for production deployment where startup time, image size, speed and memory consumption are not critical. Native images are suitable for production deployments.

Conclusion

In this tutorial, we delved into the world of Quarkus containerization by exploring the Dockerfiles generated by the Quarkus CLI tool. Each Dockerfile serves a specific purpose, catering to different deployment scenarios and optimizing the trade-offs between image size, build time, and startup speed.

We started by understanding the distinct roles of each Dockerfile:

  • Dockerfile.jvm: Tailored for running a Quarkus application in JVM mode.
  • Dockerfile.legacy-jar: Similar to Dockerfile.jvm, but optimized for a Quarkus application packaged as a legacy JAR.
  • Dockerfile.native: Designed for running a Quarkus application in native mode, providing faster startup times and a smaller image size.
  • Dockerfile.native-micro: An optimized version of Dockerfile.native, specifically focused on creating a minimal Docker image. Following the explanations, we provided step-by-step instructions for building and running each Docker image. Whether you prefer Docker or Podman, the commands provided are interchangeable, accommodating your environment setup.

In the final section, we compared the created images based on their size, build time, and startup time. The tabulated results offer a comprehensive overview of the trade-offs associated with each Quarkus application mode. Developers and operators can use this information to make informed decisions based on specific requirements, such as image size and startup time.

As we conclude this tutorial, it's essential to recognize that the choice of Dockerfile and deployment mode depends on the context and goals of your application. Whether aiming for fast development iterations, optimized production deployments, or minimal resource consumption, Quarkus provides the flexibility to meet diverse needs. Experiment with the different Dockerfiles, analyze the trade-offs, and tailor your Quarkus application deployment to achieve the optimal balance for your use case.