DEV Community

Cover image for How to build a native Linux app of a Quarkus project from another OS using a container
Yassine Benabbas for Technology at Worldline

Posted on • Edited on

How to build a native Linux app of a Quarkus project from another OS using a container

Quarkus qualifies itself as SUPERSONIC/SUBATOMIC/JAVA partly thanks to its support for GraalVM. This post shows how to build a GraalVM native image for Linux from using a container, allowing to perform this task from Windows or macOS.

Motivation

GraalVM is a high performance JDK distribution which brings many interesting features such as supporting non JVM languages (such as Python). Among the features of this JDK, the one that we're interested in here is called native image, which compiles Java code into a native binary. This means that the resulting binary will contain machine code instead of the traditional bytecode. Theoretically, we would gain in performance and startup speed.

Quarkus allows to generate a native image for the current platform quite easily. However, since most servers run on Linux, we would like to find the simplest way to generate a native Linux binary. Unfortunately, GraalVM does not seem to support cross-compilation yet. Thus, we need to find other alternatives. Hopefully, Quarkus provide a very accessible technique, described here, which requires to have a container engine installed and does not require to install GraalVM.

In this post, we'll try out to use a container to build the native image and we'll use podman as the container engine.

Prerequisites

Please read my previous post which describes how to install the prerequisites which are summarized as follows:

  • Java JDK 17
  • podman
  • On Windows, WSL needs to be installed and enabled

You can verity that you're good to go by running these commands.

java --version
podman run hello-world
Enter fullscreen mode Exit fullscreen mode

We don't need to install GraalVM nor any native compilation toolchain as they will be handled by the container which will build the native image.
That's what makes this technique so easy !.

Sample project

We're going to use the sample project developed during a previous post. It consists of a Quarkus Rest API that uses a PostgreSQL database for development and a H2 in-memory database for production. For the sake of simplification, H2 is chosen in because it makes the production app autonomous and easier to test. The application.yaml file is shown below:

greeting:
  message: "hello"

quarkus:
  datasource:
    db-kind: PostgreSQL

"%prod":
  quarkus:
    datasource:
      db-kind: H2
      jdbc:
        url: jdbc:h2:mem:default
Enter fullscreen mode Exit fullscreen mode

After cloning the project, please run the following commands to verify that we're ready to go:

  • Run tests: .\gradlew test or in Windows .\gradlew.bat test
  • Run the server in production mode: .\gradlew quarkusDev -Dquarkus.profile=prod

The backtick character is required on PowerShell for arguments that have a dot .. So, -Dquarkus.profile=prod becomes (backtick)-Dquarkus.profile=prod in PowerShell, and so on.

Checking the production profile locally is recommended because the native image will use that profile.
Once you're ready, let's create the native image.

Creating the native image using a container

Open a terminal on the project folder and run this command:

.\gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=podman
Enter fullscreen mode Exit fullscreen mode

This argument -Dquarkus.native.container-runtime=podman allows to use podman as a container tool.

The command should finish with an output similar to this one:

------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /project/testcontainers-demo-1.0.0-SNAPSHOT-runner (executable)
 /project/testcontainers-demo-1.0.0-SNAPSHOT-runner-build-output-stats.json (json)
 /project/testcontainers-demo-1.0.0-SNAPSHOT-runner-timing-stats.json (raw)
 /project/testcontainers-demo-1.0.0-SNAPSHOT-runner.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'testcontainers-demo-1.0.0-SNAPSHOT-runner' in 3m 34s.
Enter fullscreen mode Exit fullscreen mode

The native app (also called native image) is /project/testcontainers-demo-1.0.0-SNAPSHOT-runner

Running the Linux app from other OSes

We'll be exploring two techniques even if there may be more, such using a virtual machine.

Using WSL on Windows

On windows, it's quite easy to test thanks to WSL:

wsl # open a WSL session
./build/testcontainers-demo-1.0.0-SNAPSHOT-runner
Enter fullscreen mode Exit fullscreen mode

The output should display this:

__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-12-06 22:42:05,809 INFO  [io.quarkus] (main) testcontainers-demo 1.0.0-SNAPSHOT native (powered by Quarkus 2.14.2.Final) started in 0.942s. Listening on: http://0.0.0.0:8
080
2022-12-06 22:42:05,828 INFO  [io.quarkus] (main) Profile prod activated.
2022-12-06 22:42:05,828 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, config-yaml, hibernate-orm, hibernate-orm-panache, hibernate-orm-panache-kotlin, jdbc-h2, j
dbc-postgresql, kotlin, narayana-jta, resteasy-reactive, resteasy-reactive-jackson, resteasy-reactive-kotlin-serialization, smallrye-context-propagation, vertx]
^C2022-12-06 22:42:12,136 INFO  [io.quarkus] (Shutdown thread) testcontainers-demo stopped in 0.012s
Enter fullscreen mode Exit fullscreen mode

This means that the server is up and running.

Using a container

We can also test the native Linux app by running it from a container. This allows to run the app from any OS as long as there is a tool that runs containers. But, we need to first create the image.

Hopefully, projects generated using Quarkus starter already provides the configuration file, located in src/main/docker/Dockerfile.native-micro that is used by the container tool in order to generate the image that hosts and runs the native Linux app. It just requires that the native Linux app is already generated, which we already did. So, let's run this command:

podman build -f src/main/docker/Dockerfile.native-micro -t IMAGE_NAME .
Enter fullscreen mode Exit fullscreen mode

Please replace IMAGE_NAME with the desired name of the image, such as: myapp/native-server.

You can test the image by running podman run IMAGE_NAME_OR_ID and upload it to a container registry by running podman push IMAGE_NAME_OR_ID REGISTRY_NAME. IMAGE_NAME_OR_ID REGISTRY_NAME corresponds to either the name of the image that you used earlier, or its ID that you can get by running podman images.

If running the image fails, it may be due to a missing execution permission for the native app. Please add a RUN chmod +x /work/application in the dockerfile src/main/docker/Dockerfile.native-micro to fix this. The file that I used is shown below:

FROM quay.io/quarkus/quarkus-micro-image:1.0
WORKDIR /work/
RUN chown 1001 /work \
    && chmod "g+rwX" /work \
    && chown 1001:root /work
COPY --chown=1001:root build/*-runner /work/application
RUN chmod +x /work/application

EXPOSE 8080
USER 1001

CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
Enter fullscreen mode Exit fullscreen mode

You can run my generated image hosted on quay.io as an example: podman run quay.io/yostane/quarkus-jvm-demo/quarkus-jvm-demo-micro.

podman run quay.io/yostane/quarkus-jvm-demo/quarkus-jvm-demo-micro
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-12-07 20:19:51,660 INFO  [io.quarkus] (main) testcontainers-demo 1.0.0-SNAPSHOT native (powered by Quarkus 2.14.2.Final) started in 0.357s. Listening on: http://0.0.0.0:8080
2022-12-07 20:19:51,669 INFO  [io.quarkus] (main) Profile prod activated.
2022-12-07 20:19:51,669 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, config-yaml, hibernate-orm, hibernate-orm-panache, hibernate-orm-panache-kotlin, jdbc-h2, jdbc-postgresql, kotlin, narayana-jta, resteasy-reactive, resteasy-reactive-jackson, resteasy-reactive-kotlin-serialization, smallrye-context-propagation, vertx]
Enter fullscreen mode Exit fullscreen mode

Conclusion

This post showed how to generate a native Linux image of a Quarkus application in a very simple and straightforward way. This native app does not a require a JRE and is "theoretically" faster and more optimized than a JVM counterpart.

We used a container to generated the image which handled all the native complication toolchain. It also allowed to cross-compile to Linux. I was really surprised to see how it's easy to generate a native image using this technique. So, why not try it out on your side ?

Top comments (0)