Building Container-ready native applications with Quarkus

Learn how to build container-ready native executables using Quarkus 3.x, Java 17/21, and GraalVM/Mandrel. This tutorial covers zero-install containerized native builds, local compilation using GraalVM, packaging Docker images (JVM, Native, Distroless), and deploying Jakarta EE 10 reactive applications to OpenShift using RESTEasy Reactive (Quarkus REST) and SmallRye Mutiny.

Here is our updated guide for Quarkus 3.x. Today we will learn how to create native executable applications with amazingly fast startup times and minimal memory footprint. Next, we will package our Jakarta EE application into a Container image and deploy it on a Cloud Platform like OpenShift.


In our first tutorial – Getting started with Quarkus – we learned that the Quarkus Maven plugin creates a native profile which you can use to build native executables:

<profiles>
    <profile>
      <id>native</id>
      <activation>
        <property>
          <name>native</name>
        </property>
      </activation>
      <properties>
        <skipITs>false</skipITs>
        <quarkus.native.enabled>true</quarkus.native.enabled>
      </properties>
    </profile>
</profiles>

On the other hand, you can use the default profile to build a standard JVM fast-jar application.

With Quarkus 3.x, all applications leverage Jakarta EE 10 specifications (replacing legacy javax.* packages with jakarta.* like jakarta.ws.rs.* and jakarta.enterprise.context.*) alongside high-performance reactive components powered by RESTEasy Reactive (Quarkus REST) and SmallRye Mutiny.

You can build Quarkus native executables in two ways:

  • Use a Container image of GraalVM or Mandrel. This option does not require installing GraalVM locally.
  • Install GraalVM or Mandrel locally on your system and use it to compile your code.

Let’s explore both options.

1) Building a Native executable with Quarkus – the easy way

This is the fastest option to build a Quarkus native executable because it runs the compilation inside a container. Make sure your container engine (Docker or Podman) is running:

service docker start

Next, build the native executable using the Quarkus CLI or Maven:

quarkus build --native -Dquarkus.native.container-build=true

Or using Maven directly:

./mvnw package -Pnative -Dquarkus.native.container-build=true

At the end of the build, you can run the generated native binary from your target directory:

./target/code-with-quarkus-1.0.0-SNAPSHOT-runner

2) Building a Native executable with GRAALVM / Mandrel

In the second option, we install GraalVM or Red Hat's Mandrel distribution (a downstream distribution of GraalVM specifically tuned for OpenJDK and Quarkus).

GraalVM releases are available at: https://github.com/graalvm/graalvm-ce-builds/releases

With the latest versions of Quarkus 3.x, a minimum of JDK 17 or JDK 21 is required.

Download the appropriate JDK 17+ archive from https://github.com/graalvm/mandrel/releases or https://github.com/graalvm/graalvm-ce-builds/releases

Set your GRAALVM_HOME environment variable pointing to your downloaded directory:

export GRAALVM_HOME=/path/to/graalvm-or-mandrel
export JAVA_HOME=$GRAALVM_HOME

Note: In modern GraalVM releases for JDK 17 and JDK 21, the native-image tool is pre-packaged inside the distribution, so running gu install native-image is no longer needed.

Building the native application

Now let's build the Jakarta EE application discussed in our guide Getting started with Quarkus. Move to the root directory of your project and trigger the build with ./mvnw verify -Pnative:

$ ./mvnw verify -Pnative

You should see log output similar to the following upon starting the application:

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-10-15 10:07:14,688 INFO  [io.quarkus] (main) demo 1.0.0-SNAPSHOT native (powered by Quarkus 3.15.0) started in 0.018s. Listening on: http://0.0.0.0:8080
2024-10-15 10:07:14,688 INFO  [io.quarkus] (main) Profile prod activated. 
2024-10-15 10:07:14,688 INFO  [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy-reactive, smallrye-context-propagation]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.423 s - in com.sample.NativeGreetingResourceIT

Note: If you get a native compilation error regarding missing header files (e.g., zlib.h), install the system development libraries:

# Ubuntu / Debian
sudo apt-get install libz-dev

# Fedora / RHEL / CentOS
sudo dnf install zlib-devel

Once the native build completes, check the target folder:

$ ls -al target
drwxrwxr-x. 4 francesco francesco     4096 Oct 15 17:38 classes
-rw-rw-r--. 1 francesco francesco     4722 Oct 15 10:05 demo-1.0.0-SNAPSHOT.jar
drwxrwxr-x. 3 francesco francesco     4096 Oct 15 10:07 demo-1.0.0-SNAPSHOT-native-image-source-jar
-rwxrwxr-x. 1 francesco francesco 35400768 Oct 15 10:07 demo-1.0.0-SNAPSHOT-runner
drwxrwxr-x. 2 francesco francesco     4096 Oct 15 10:07 failsafe-reports
drwxrwxr-x. 3 francesco francesco     4096 Oct 15 17:38 generated-sources
drwxrwxr-x. 3 francesco francesco     4096 Oct 15 17:38 generated-test-sources
drwxrwxr-x. 2 francesco francesco     4096 Oct 15 10:07 kubernetes
drwxrwxr-x. 2 francesco francesco     4096 Oct 15 17:38 maven-archiver
drwxrwxr-x. 3 francesco francesco     4096 Oct 15 17:38 maven-status
drwxrwxr-x. 3 francesco francesco     4096 Oct 15 17:38 quarkus
drwxrwxr-x. 5 francesco francesco     4096 Oct 15 10:05 quarkus-app
-rw-rw-r--. 1 francesco francesco      272 Oct 15 10:07 quarkus-artifact.properties
-rw-rw-r--. 1 francesco francesco      624 Oct 15 10:07 quarkus.log
drwxrwxr-x. 2 francesco francesco     4096 Oct 15 17:38 surefire-reports
drwxrwxr-x. 3 francesco francesco     4096 Oct 15 10:07 test-classes

Let's execute the binary directly:

$ target/demo-1.0.0-SNAPSHOT-runner 
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-10-15 10:10:09,541 INFO  [io.quarkus] (main) demo 1.0.0-SNAPSHOT native (powered by Quarkus 3.15.0) started in 0.019s. Listening on: http://0.0.0.0:8080
2024-10-15 10:10:09,542 INFO  [io.quarkus] (main) Profile prod activated. 
2024-10-15 10:10:09,542 INFO  [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy-reactive, smallrye-context-propagation]

In less than 20 milliseconds, the non-blocking Jakarta EE REST endpoint is live! Test it via curl:

$ curl http://localhost:8080/hello

Building Container images for Quarkus applications

Quarkus provides ready-to-use Dockerfiles inside the project folder src/main/docker:

src
├── main
│   ├── docker
│   │   ├── Dockerfile.jvm
│   │   ├── Dockerfile.legacy-jar
│   │   ├── Dockerfile.native
│   │   └── Dockerfile.native-distroless
  • Dockerfile.jvm: Builds a lightweight container image based on Quarkus fast-jar layout located under target/quarkus-app/.
  • Dockerfile.native: Packages the compiled microsecond-booting native executable.
  • Dockerfile.legacy-jar: Builds a container using the legacy monolithic JAR layout.
  • Dockerfile.native-distroless: Creates an ultra-minimal distroless container image containing only your native application binary and system C runtime dependencies without OS shells.

Standard Quarkus container images use Red Hat Universal Base Image (UBI) as their base:

FROM registry.access.redhat.com/ubi9/ubi-minimal:latest

What is an UBI Image? A Red Hat Universal Base Image (UBI) provides stable, enterprise-grade, certified container base images for containerized workloads running on OpenShift, Podman, or Kubernetes.

Building the JVM Container image

To build a standard JVM container image of your application, first package the project:

./mvnw package

Then run Docker to build the image:

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

Run the container:

docker run -i --rm -p 8080:8080 quarkus/hello-quarkus

Building the Native Container image

To compile native binaries directly for container runtime, pass -Dquarkus.native.container-build=true during the native build:

./mvnw package -Pnative -Dquarkus.native.container-build=true

Next, build the native container image:

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

Check the generated image size:

$ docker images
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
quarkus/hello-quarkus                       latest              761947bb9d9d        25 seconds ago      115 MB

Run your containerized native Quarkus app:

docker run -i --rm -p 8080:8080 quarkus/hello-quarkus
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-10-15 08:52:54,640 INFO  [io.quarkus] (main) demo 1.0.0-SNAPSHOT native (powered by Quarkus 3.15.0) started in 0.015s. Listening on: http://0.0.0.0:8080
2024-10-15 08:52:54,640 INFO  [io.quarkus] (main) Profile prod activated. 
2024-10-15 08:52:54,640 INFO  [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy-reactive, smallrye-context-propagation]

Test the response with curl:

$ curl http://localhost:8080/hello

Deploying Quarkus applications on OpenShift

Deploying Quarkus 3.x workloads on OpenShift or Kubernetes is effortless. You can test this locally using Red Hat OpenShift Local (formerly CodeReady Containers).

Check our detailed guide to set up your local cluster: Getting started with Code Ready Containers

Start your local OpenShift environment:

$ crc start

Create a project space for Quarkus:

$ oc new-project quarkus
Now using project "quarkus" on server "https://api.crc.testing:6443".

Add the OpenShift extension to your Quarkus project:

$ ./mvnw quarkus:add-extension -Dextensions="openshift"

To automatically create an OpenShift Route upon deployment, add this property inside src/main/resources/application.properties:

quarkus.openshift.expose=true

To build and automatically deploy a JVM application directly into OpenShift, execute:

$ ./mvnw clean package -Dquarkus.kubernetes.deploy=true

To deploy a native application binary instead, enable the native profile:

$ ./mvnw clean package -Pnative -Dquarkus.kubernetes.deploy=true

Verify that your deployment and pods are active:

oc get pods
NAME            READY   STATUS      RESTARTS   AGE
demo-1-5p4rb    1/1     Running     0          4m57s
demo-1-build    0/1     Completed   0          5m42s
demo-1-deploy   0/1     Completed   0          5m

If you didn't expose the route automatically in application.properties, expose it manually via oc:

$ oc expose svc/demo
route.route.openshift.io/demo exposed

You can now test the live OpenShift endpoint:

quarkus 2.0 tutorial getting started

If you want to learn how to leverage the Mandrel distribution for building native images, check out our guide: Building Quarkus native applications with Mandrel

Frequently Asked Questions (FAQs)

1. What are the Java and GraalVM requirements for Quarkus 3.x?

Quarkus 3.x requires a minimum of Java 17 (with full support for Java 21). Native compilation requires GraalVM or Mandrel releases compatible with JDK 17 or JDK 21.

2. How does RESTEasy Reactive differ from classic RESTEasy in Quarkus 3?

RESTEasy Reactive (Quarkus REST) is engineered specifically for Quarkus on top of Vert.x and SmallRye Mutiny. It executes non-blocking requests on event loops without thread context switching, reducing memory usage and startup time while maintaining full compatibility with Jakarta RESTful Web Services (jakarta.ws.rs.*).

3. Do I need to install GraalVM locally to build native binaries?

No. By passing -Dquarkus.native.container-build=true, Quarkus will pull a builder container image (such as Mandrel) and compile your native binary inside Docker or Podman without requiring local GraalVM installations.

Conclusion

Quarkus 3.x brings modern Jakarta EE 10 standards, high-throughput reactive execution via RESTEasy Reactive and Mutiny, and effortless containerization. Whether compiling JVM fast-jars or native executables via GraalVM/Mandrel, Quarkus provides instant startup times and minimal resource footprint ideal for cloud-native Kubernetes and OpenShift deployments.


Recommended Articles

Mastering QuarkusIO: Building Supersonic Java Applications with Cloud-Native Features

Learn how to build supersonic Java applications using QuarkusIO, a cloud-native framework optimized for microservices. #Quarkus #Java #CloudNative

Quarkus vs Spring Boot: Building Microservices with QuarkusIO

Discover how Quarkus and Spring Boot compare in building microservices. Learn about the Microprofile API and QuarkusIO's core architecture.

Deploy Quarkus Applications on OpenShift with Code Ready Containers

Learn how to deploy Quarkus applications in containers and specifically on OpenShift Paas Cloud platforms using Red Hat Code Ready Containers.

Building Quarkus Native Applications with Mandrel | Enterprise Java Tutorials

Learn how to create native builds for Quarkus applications using Mandrel, a downstream open source distribution of GraalVM edition.