Home / News / Build a container image for a Quarkus project using Buildpacks

Build a container image for a Quarkus project using Buildpacks

This post explains how to use the Quarkus Container Image Buildpack extension to build container images without a Dockerfile, simplifying your CI/CD process.

This is part of a series on building your applications with Cloud Native Computing Foundation (CNCF) Buildpacks. Catch up on the previous articles here:

Since its inception, the Quarkus runtime has had a goal to improve and simplify the developer experience, especially in the fields of containerization and application deployment for Kubernetes. 

Out of the box, you can build a container image with Quarkus using either Podman or Docker as a container engine, along with a Dockerfile. A Dockerfile is included in every generated Quarkus project. 

To support a Buildpacks build, however, you must install a new Quarkus extension. This exension adds support for Quarkus through the Quarkus Container Image framework. The Container Image Buildpack extension makes use of the Java Buildpack Client. This client executes the different phases of a Buildpacks build by driving the lifecycle binaries within different containers. 

From this point of view, the Java Buildpack Client and the Quarkus build using it behave in a similar way as the Paketo Pack client. Both are implementations of the Buildpacks Platform specification.

This Quarkus extension not only allows you to perform a build and push the image to a container registry, but if you use the Paketo UBI builder image, you also benefit from the presence of the Quarkus buildpack. This buildpack configures the necessary environment variable and Maven arguments to compile the application.

Setup

Note: Before you use Quarkus, we recommend that you review the Getting Started guide.

Prerequisites

  • Podman or Docker engine installed
  • JDK 21 or later
  • Maven 3.9 or later

Create a Quarkus “Hello” application

First, create a Quarkus application that exposes a /hello endpoint. You can use either the code generator website or the following Maven command executed within a terminal:

mvn io.quarkus.platform:quarkus-maven-plugin:3.24.2:create \
       -DprojectGroupId=io.quarkus \
       -DprojectArtifactId=hello \
       -DprojectVersion=1.0 \
       -DplatformVersion=3.24.2 \
       -Dextensions='resteasy'

Launch it locally

When the project is created, use Quarkus dev mode to access the console at http://localhost:8080. Then, click the /hello endpoint to see if you get the “Hello RESTEasy” response from the runtime.

cd hello
mvn quarkus:dev

Add the container-image-buildpack extension

Now that you have validated that the project can be compiled and launched locally, it’s time to add the missing extension and perform a Buildpacks build.

mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-container-image-buildpack"

Note: To verify that the extension was included, open the pom.xml file and verify that the Buildpack extension was added.

> cat pom.xml | grep buildpack
            <artifactId>quarkus-container-image-buildpack</artifactId>

Build a container image

To build the container image, simply execute the following command:

mvn install -Dquarkus.container-image.build=true

At the beginning of the Quarkus Buildpacks processor execution, different messages will appear, such as the engine detected, the builder image selected, and so on.

[INFO] --- quarkus:3.24.2:build (default) @ hello ---
[INFO] [io.quarkus.container.image.buildpack.deployment.BuildpackProcessor] Starting (local) container image build for jar using buildpack.
[INFO] [io.quarkus.container.image.buildpack.deployment.BuildpackProcessor] Initiating Buildpack build
[INFO] Testing for podman/docker...
[INFO] Podman detected, configuring.
[INFO] Buildpack build requested with config:
- builder docker.io/paketocommunity/builder-ubi-base:latest
- output cmoullia/hello:1.0
- logLevel info
- dockerHost unix:///var/folders/28/g86pgjxj0wl1nkd_85c2krjw0000gn/T/podman/podman-machine-default-api.sock
- dockerSocket /run/user/501/podman/podman.sock
- useDaemon true
...

For this article, we are using the UBI 8 Builder image, which is the default image for the Quarkus extension and has been covered in this series of articles. 

One of the key features of this image, which was designed on top of the Red Hat Enterprise Linux OS, is that it uses the image-extension feature of Buildpacks to install the appropriate JDK version for the application into the build environment (for example, OpenJDK 21 from a RPM).

Customize the build

To customize your build, you can use one of the buildpack properties referenced in the extension documentation using the following convention: 

-Dquarkus.buildpack.<PROPERTY_NAME>=<PROPERTY_VALUE>

Note: You can also combine them with the properties of the Quarkus container-image extension to configure the name of the image, the registry, etc. 

If you want to use a specific version of the builder image and enhance the logging level, then pass the following parameters to the Maven package goal:

mvn package -Dquarkus.container-image.build=true
-Dquarkus.buildpack.jvm-builder-image=paketobuildpacks/builder-ubi8-base:0.1.42
-Dquarkus.buildpack.log-level=debug

The build could fail for different reasons when you run the command. In our previous build, the process failed because it attempted to use a different JVM level than the one specified in the generated Maven pom.xml. 

2025-07-14T14:37:08+02:00       [INFO] ------------------------------------------------------------------------
2025-07-14T14:37:08+02:00       [INFO] BUILD FAILURE
2025-07-14T14:37:08+02:00       [INFO] ------------------------------------------------------------------------
2025-07-14T14:37:08+02:00       [INFO] Total time:  27.852 s
2025-07-14T14:37:08+02:00       [INFO] Finished at: 2025-07-14T12:37:08Z
2025-07-14T14:37:08+02:00       [INFO] ------------------------------------------------------------------------
2025-07-14T14:37:08+02:00       [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.14.0:compile (default-compile) on project hello: Fatal error compiling: error: release version 21 not supported -> [Help 1]

To resolve this, simply provide an environment variable formatted using the convention BP_JVM_VERSION to tell the Paketo Java Buildpack extension that we would like to use JDK 21 during the build: 

mvn package -Dquarkus.container-image.build=true -Dquarkus.buildpack.builder-env.BP_JVM_VERSION=21

Alternatively, if you prefer to use the Quarkus application properties configuration file, you can declare those properties within this file and simply run mvn package:

// src/main/resources/application.properties
quarkus.container-image.build=true
quarkus.buildpack.jvm-builder-image=paketobuildpacks/builder-ubi8-base:0.1.42
quarkus.buildpack.builder-env.BP_JVM_VERSION=21

What about the Dockerfile?

As you might have noticed, no container file (for example, a Dockerfile) was created or used as part of the Buildpacks build process. By nature, the CNCF Buildpacks technology doesn’t rely on such files to perform the build. Instead, it uses a collection of Buildpacks included in a plan that is calculated during the detect phase.

If you look at the log file, you should see messages reporting what the Buildpacks detected or skipped.

2025-07-15T12:26:55+02:00 ======== Output: paketo-buildpacks/quarkus@0.6.1 ========
2025-07-15T12:26:55+02:00
2025-07-15T12:26:55+02:00 Paketo Buildpack for Quarkus 0.6.1
2025-07-15T12:26:55+02:00   https://github.com/paketo-buildpacks/quarkus
2025-07-15T12:26:55+02:00   Build Configuration:
2025-07-15T12:26:55+02:00     $BP_MAVEN_POM_FILE  pom.xml  the location of the main pom.xml file, relative to the application root
2025-07-15T12:26:55+02:00 ======== Output: paketo-buildpacks/leiningen@4.12.2 ========
2025-07-15T12:26:55+02:00 SKIPPED: project.clj could not be found in /workspace/project.clj
2025-07-15T12:26:55+02:00 ======== Output: paketo-buildpacks/clojure-tools@2.15.1 ========
2025-07-15T12:26:55+02:00 SKIPPED: no 'deps.edn' file found in the application path
2025-07-15T12:26:55+02:00 ======== Output: paketo-buildpacks/gradle@7.19.3 ========
2025-07-15T12:26:55+02:00   Build Configuration:
2025-07-15T12:26:55+02:00     $BP_EXCLUDE_FILES                                                                       ...
2025-07-15T12:26:55+02:00     $BP_NODE_PROJECT_PATH                                                                   configure a project subdirectory to look for `package.json` and `yarn.lock` files
2025-07-15T12:26:55+02:00 SKIPPED: No plans could be resolved

Finally, the list of Buildpacks participating to a build will be listed:

2025-07-15T12:26:55+02:00 13 of 30 buildpacks participating
2025-07-15T12:26:55+02:00 paketo-community/ubi-java-extension 0.2.0
2025-07-15T12:26:55+02:00 paketo-community/ubi-java-buildpack 0.1.1
2025-07-15T12:26:55+02:00 paketo-buildpacks/quarkus           0.6.1
2025-07-15T12:26:55+02:00 paketo-buildpacks/ca-certificates   3.10.3

During the build, the lifecycle calls its “build” executable for each Buildpack. The executable can install, for example, the Maven tool, a certificate, or the required JDK. It also collects the arguments for Quarkus to pass to the Maven command.

To build a Quarkus project, its Buildpack then sets the following environment variables for a non-native build.

BP_MAVEN_BUILT_ARTIFACT=”target/quarkus-app/lib/ target/quarkus-app/*.jar target/quarkus-app/app/ target/quarkus-app/quarkus/"
BP_MAVEN_BUILD_ARGUMENTS=”package -DskipTests=true -Dmaven.javadoc.skip=true -Dquarkus.package.type=fast-jar"

Create a container using the image

If the Buildpacks build succeeded, you can now create a container locally using the built image and test if the /hello endpoint replies.

// Command to be used with the container engine 
podman run -i --rm -p 8080:8080 <USER_NAME>/<MAVEN_ARTIFACT_ID>:<MAVEN_VERSION>

// Example
podman run -i --rm -p 8080:8080 cmoullia/hello:1.0 &
curl http://localhost:8080/hello

Build against a container registry

By default, the Quarkus Buildpack extension builds the image against the local container engine (or daemon) without pushing the image to a registry. If this is required, you can use the following Quarkus properties from the Quarkus container-image extension to configure the registry credentials, such as the image name: 

quarkus.container-image.build=true
quarkus.container-image.push=true

quarkus.container-image.image=<REGISTRY_SERVER>/<REGISTRY_ORG>/<NAME>:<VERSION>
quarkus.buildpack.registry-user."<REGISTRY_SERVER>"=<REGISTRY_USER>
quarkus.buildpack.registry-password."<REGISTRY_SERVER>"=<REGISTRY_PASSWORD>

// Example
quarkus.container-image.build=true
quarkus.container-image.push=true

quarkus.container-image.image=quay.io/ch007m/quarkus-hello:1.0
quarkus.buildpack.registry-user."quay.io"=quarkus
quarkus.buildpack.registry-password."quay.io"=AZERTYUIOPQSDFGHJKLMWXCVBN1234567890

What else?

The Buildpack capability provided by the Quarkus Container Image extension, demonstrated in this article, is implemented within a reusable Java Buildpack Client that runs the lifecycle binaries in dedicated containers.

This Java client differs from the Spring Boot project’s implementation of a Buildpack platform, which is available as part of its Maven plug-in. While Spring Boot also supports building container images using Buildpacks, their platform implementation is older. It does not support Image Extensions, so it can’t run the UBI builder image.   

The post Build a container image for a Quarkus project using Buildpacks appeared first on Red Hat Developer.

Tagged: