Comparing Various Docker Images for Quarkus Applications
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.