Using CircleCI to Build a Native Quarkus Application

An important part of any software project, Quarkus applications included, is continuous integration. A popular tool in the CI space is CircleCI, especially with open source projects. There is lots of documentation out there about how to configure CircleCI for a standard Maven project. However, building a native Quarkus executable requires one or two extra steps. This blog post describes how to make it happen.

If you’re in a hurry, just scroll down to the full (and annotated) example at the bottom of this post.

Anatomy of a CircleCI Job

If you aren’t familiar with CircleCI, a good place to start might be their configuration documentation reference. This blog post assumes you have a basic familiarity with CircleCI and just want a little bit of help configuring a native Quarkus build. For the most part, the job is configured just like any standard Maven build, including:

  • Checkout from source

  • Dependency caching

  • Build

  • Test

However for a native Quarkus build, the extra configuration centers around installing GraalVM and then performing (and verifying) the native image build.

Docker vs. Machine

CircleCI allows three types of "executors" (the runtime environment used to perform the build job). These are:

  • docker

  • machine

  • macos

It seems that when performing a build using docker it is very common to run out of memory. So it is recommended that you use machine for your executor. Fortunately, the default virtual machine that is used already contains most of the build tools needed, including (but by no means limited to):

  • Java

  • Maven

  • Chrome

  • PhantomJS

The only build tool you will need that is not pre-installed is GraalVM…​

GraalVM and native-image

In order to successfully build a Quarkus native app using the mvnw package -Pnative command, GraalVM must be installed and available at the GRAALVM_HOME environment variable. This means that your build config must include the appropriate command(s) needed to download, unpack, and install GraalVM. And you also must configure the GRAALVM_HOME environment variable appropriately. There are many ways to install GraalVM, but just one example might be something like this:

      - run:
          name: Install GraalVM
          command: curl https://github.com/oracle/graal/releases/download/vm-19.1.1/graalvm-ce-linux-amd64-19.1.1.tar.gz -O -J -L && tar xfz graalvm-ce-linux-amd64-19.1.1.tar.gz && mv graalvm-ce-19.1.1 .graalvm && rm graalvm-ce-linux-amd64-19.1.1.tar.gz

Once GraalVM is installed, you will also need to install native-image, which is an add-on to GraalVM. This is done by using the gu utility that comes with GraalVM. In the CircleCI config, it might look something like this:

      - run:
          name: Install native-image
          command: $GRAALVM_HOME/bin/gu install native-image

As mentioned, you will need to set the GRAALVM_HOME environment variable. You can either do that globally in the environment section of the config, or you can set it separately for each run command. It’s usually easiest to do the former:

    environment:
      GRAALVM_HOME: /home/circleci/repo/.graalvm

Native Build and Verify

After GraalVM and native-image are installed, you can simply create one or more run commands to build and verify your native Quarkus app. It will look like this:

      - run:
          name: Build (Native)
          command: ./mvnw clean package -Pnative -DskipTests -Dmaven.test.skip=true
          no_output_timeout: 30m
      - run:
          name: Verify (Native)
          command: ./mvnw verify -Pnative
          no_output_timeout: 30m

You will notice that both commands have an additional setting for no_output_timeout. It seems that the native-image tool does not always complete in a timely fashion. Sometimes it is quick, but sometimes it takes a little bit of time - more than the CircleCI default timeout of 10 minutes. I’m sure that as the GraalVM native-image tool matures, this will be improved (both the total time it takes to run and the variability). Until then, simply increasing the timeout seems to work well.

Full Example (Annotated)

You probably just skipped all of the information above this point and want to copy/paste the example, don’t you? Yeah that’s what I would do too! Here is a full working example (.circleci/config.yml) of a native Quarkus build job in CircleCI:

#
# Native Quarkus CircleCI configuration file
#
version: 2
jobs:
  build:
    # Use "machine" instead of e.g. "docker" for better/faster results
    machine: true
    # Uses a "medium" sized machine - maybe increase this to "large" if you pay for CircleCI
    resource_class: medium
    working_directory: ~/repo
    environment:
      MAVEN_OPTS: -Xmx6400m
      # Set the GRAALVM_HOME env variable to the location where we will be installing GraalVM
      GRAALVM_HOME: /home/circleci/repo/.graalvm
    steps:
      # Checkout the source code
      # ########################
      - checkout

      # Restore any files we may have cached
      # ########################
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "pom.xml" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      # Download maven dependencies so that we can cache them
      # ########################
      - run:
          name: Download Dependencies
          command: mvn dependency:go-offline
      # Cache the maven dependencies
      - save_cache:
          paths:
            - ~/.m2
          key: v1-dependencies-{{ checksum "pom.xml" }}

      # Standard maven build and test phases - does not perform a native build (or verify)
      # ########################
      - run:
          name: Build (Standard)
          command: ./mvnw clean package -DskipTests -Dmaven.test.skip=true
      - run:
          name: Verify (Standard)
          command: ./mvnw test

      # Install GraalVM and native-image, needed for a native Quarkus build
      # ########################
      - run:
          name: Install GraalVM
          command: curl https://github.com/oracle/graal/releases/download/vm-19.1.1/graalvm-ce-linux-amd64-19.1.1.tar.gz -O -J -L && tar xfz graalvm-ce-linux-amd64-19.1.1.tar.gz && mv graalvm-ce-19.1.1 .graalvm && rm graalvm-ce-linux-amd64-19.1.1.tar.gz
      - run:
          name: Install native-image
          command: $GRAALVM_HOME/bin/gu install native-image

      # Perform a native Quarkus build and verify
      # ########################
      - run:
          name: Build (Native)
          command: ./mvnw clean package -Pnative -DskipTests -Dmaven.test.skip=true
          no_output_timeout: 30m
      - run:
          name: Verify (Native)
          command: ./mvnw verify -Pnative
          no_output_timeout: 30m