Containerize This! A Hands-On Guide to Quarkus with Buildpacks
Learn how to containerize your Java applications the modern way, using Quarkus and CNCF Buildpacks for faster builds, smaller images, and zero boilerplate.
If you've ever wanted to ship your Quarkus application in a container without writing a Dockerfile, crafting multi-stage builds, or worrying about base images, this tutorial is for you. Today we're diving into Cloud Native Buildpacks, a standard backed by the CNCF that brings automation and structure to container builds. And we're pairing it with Quarkus, which just happens to have first-class integration for Buildpacks. No boilerplate, no surprises. Just clean, efficient Java containerization.
Let’s walk through building a Quarkus application from scratch and containerizing it using Buildpacks, while exploring what makes this approach both powerful and practical for modern Java developers.
Understanding Buildpacks (Before You Touch the Terminal)
Before we begin, it's worth getting clear on what Buildpacks are and why they’re gaining traction, especially in enterprise Java environments. Buildpacks originated at Heroku and have since been standardized by the Cloud Native Computing Foundation. At their core, they’re a toolchain that turns source code into OCI-compliant container images. They do this by detecting your app’s runtime, downloading the right build tools (like the JDK or Maven), and constructing your final container image in a layered, optimized, and production-ready way.
Unlike Dockerfiles, Buildpacks are declarative and standardized. They take care of all the scaffolding: choosing base images, setting up the environment, and caching dependencies. The result is a secure, repeatable container image, without having to write a line of Docker syntax. Think of it as infrastructure automation for your builds.
Why Use Buildpacks with Quarkus?
Many Java frameworks treat containerization as an afterthought. Quarkus does not. Quarkus integrates deeply with container image building tools and makes Buildpacks a first-class citizen through the container-image-buildpack
extension. That means you don’t need to customize anything to benefit from Buildpacks. They work seamlessly with Quarkus' fast-jar packaging format and are wired directly into the Maven lifecycle. You simply run mvn package
, and if configured, Quarkus will automatically trigger the Buildpack process to produce a clean container image of your app.
What makes this special is that Quarkus doesn’t require any special setup outside of Maven and a container runtime like Podman. Everything is orchestrated from the build itself, and the resulting image is portable, optimized, and ready to be deployed to Kubernetes or any OCI-compliant platform.
Step 1: Creating the Quarkus Application
Start by generating a new Quarkus project. Open your terminal and run the Quarkus Maven plugin to scaffold a simple REST application. This will give you a minimal project that exposes a basic REST endpoint, which is more than enough to demonstrate the full Buildpack flow.
mvn io.quarkus.platform:quarkus-maven-plugin:3.22.2:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=quarkus-buildpack-example \
-Dextensions='rest'
cd quarkus-buildpack-example
At this point, you’ll have a quarkus-buildpack-example
project folder containing your source code, a Maven pom.xml
, and a default /hello
endpoint implemented using Quarkus REST.
Step 2: Enabling Buildpacks in Your Project
Next, you need to tell Quarkus to support container image builds via Buildpacks. This is done by adding the container-image-buildpack
extension to your project. If you're using the Maven wrapper included in the project, you can run:
./mvnw quarkus:add-extension -Dextensions="container-image-buildpack"
This extension hooks into the build process and adds all necessary configuration options to support Buildpacks out of the box. It doesn’t change how you develop, only how your app is packaged into a container.
Step 3: Building Your Container Image with Buildpacks
Now you’re ready to build the application. Unlike a traditional Java build, this one also produces a runnable container image, thanks to Buildpacks. Run the following command:
./mvnw package -Dquarkus.container-image.build=true
This command compiles your Java code, packages it using the Quarkus fast-jar layout, and then triggers the Buildpacks lifecycle. Buildpacks will automatically detect that you're building a Java application, fetch a suitable JDK and Maven toolchain, run the build inside a containerized environment, and then assemble a runtime image containing only the JAR, the JVM, and the necessary runtime dependencies.
One of the key benefits here is that Buildpacks cache intermediate layers. That means if you make a change in your code and rebuild, it won’t redownload the JDK or re-fetch Maven dependencies—it will reuse those layers, resulting in faster builds over time.
Step 4: Verifying the Container Image
Once the build completes, you'll want to verify that the image has been created. With Podman, you can inspect the local container image cache using the appropriate command.
podman images
In the output, you should see an image named something like youruser/quarkus-buildpack-example:1.0.0-SNAPSHOT
. This name comes from your project’s Maven groupId
, artifactId
, and version
.
Step 5: Running the Application as a Container
Now that you’ve built the container image, it’s time to run it. Again, you can use Docker or Podman, whichever you prefer. The following command will start the container and expose port 8080 so you can interact with the application.
With Podman:
podman run -i --rm -p 8080:8080 youruser/quarkus-buildpack-example:1.0.0-SNAPSHOT
Your Quarkus application is now running in a clean, Buildpack-generated container.
Step 6: Verifying the Endpoint
To verify that everything works, open a browser or use curl
to hit the /hello
endpoint:
curl http://localhost:8080/hello
You should get a plain text response that says:
Hello from Quarkus REST
And just like that, you’ve built and run a Java application, containerized using industry-standard tools, without touching a Dockerfile.
Going Beyond the Basics: Advanced Buildpack Configuration
Once you’re comfortable with the basics, Quarkus gives you several configuration options to fine-tune how Buildpacks behave.
You can specify a different builder image if you want to change the base used for building and running your application. For example, to use a Red Hat UBI-based builder, add the following line to your application.properties
:
quarkus.buildpack.jvm-builder-image=registry.access.redhat.com/ubi8/openjdk-17
You can also pass environment variables to customize the build. These variables are scoped specifically to the Buildpack lifecycle and can influence behavior such as the JDK version or memory tuning parameters. For instance:
quarkus.buildpack.env.BP_JVM_VERSION=17
If you want to push your container image to a remote registry, configure the registry settings like this:
quarkus.container-image.registry=quay.io
quarkus.container-image.group=myuser
quarkus.container-image.name=quarkus-buildpack-demo
quarkus.container-image.username=myuser
quarkus.container-image.password=super-secret
Then you can include an additional flag to your build:
./mvnw package -Dquarkus.container-image.build=true -Dquarkus.container-image.push=true
This builds your image and pushes it directly to the specified registry, ready for deployment on OpenShift, Kubernetes, or any other container platform.
Final Thoughts
Quarkus and Buildpacks together deliver a compelling experience for Java developers who want to build cloud-native applications without becoming container experts. Buildpacks bring predictability, repeatability, and portability to container image creation—values that align perfectly with the philosophy behind Quarkus.
You no longer need to manage Dockerfiles, remember base image tags, or fiddle with shell scripts. Just write your Java code, build your app, and let Buildpacks handle the rest. If you want to learn even more, make sure to watch Quarkus Insights Episode 204!
Whether you’re pushing to production, building in CI, or experimenting locally, the Quarkus and Buildpack integration gives you the power of modern containerization. On your terms.