
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Java applications have historically been heavy, slow to start, and memory-hungry—traits that clash directly with the ephemeral nature of cloud-native orchestration. To successfully run Quarkus on Kubernetes, you must move beyond standard JVM deployments and embrace the framework’s "Supersonic Subatomic" design, specifically its GraalVM native compilation and container-first build extensions. This guide covers the exact configuration, Dockerfile patterns, and manifest tuning required to deploy resilient, low-latency Quarkus services in production clusters today.
How do you build an optimized container image to run Quarkus on Kubernetes?
The most common mistake engineers make when they first run Quarkus on Kubernetes is shipping the JVM mode artifact in a standard OpenJDK base image. While functional, this negates the primary value proposition of the framework: instant scaling and low memory footprint. For production workloads, especially those subject to autoscaling events or frequent rescheduling, native compilation is the standard.
You should leverage the quarkus-container-image-docker extension combined with a multi-stage Dockerfile. This approach separates the heavy build environment from the lean runtime environment, ensuring your final artifact contains only the statically linked binary and essential libraries.
Multi-stage Dockerfile for native execution
This Dockerfile uses the official Mandrel builder image (a Red Hat downstream distribution of GraalVM optimized for Quarkus) and targets the ubi-micro runtime. The result is typically under 100MB and starts in milliseconds.
## Stage 1: Build the native executable
FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 AS build
COPY --chown=quarkus:quarkus mvnw /code/mvnw
COPY --chown=quarkus:quarkus .mvn /code/.mvn
COPY --chown=quarkus:quarkus pom.xml /code/
USER quarkus
WORKDIR /code
RUN ./mvnw -B org.apache.maven.plugins:maven-dependency-plugin:3.6.0:go-offline
COPY src /code/src
RUN ./mvnw package -Pnative -DskipTests
## Stage 2: Minimal runtime
FROM registry.access.redhat.com/ubi8/ubi-micro:8.9
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --from=build /code/target/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] A critical detail often missed in tutorials is the user permission handling. The UBI-micro image does not include useradd, so we rely on UID 1001 which is pre-configured in the Quarkus ecosystem. Always run as non-root; this is mandatory for passing security audits like SOC 2 or ISO 27001 in regulated environments.
What Kubernetes resources are required to safely run Quarkus on Kubernetes?
Once you have an optimized image, you need manifests that reflect the unique behavior of native binaries. Unlike JVM apps that warm up gradually, native Quarkus applications are either ready instantly or failing immediately. Your resource definitions must account for this binary state.
I recommend integrating the quarkus-kubernetes extension during your build phase. It generates YAML automatically based on your application.properties. However, you must verify and tune these generated manifests. Blindly applying defaults leads to OOMKilled pods or failed health checks during node pressure events.
- Memory Requests vs Limits: Native executables have predictable memory usage. Set requests and limits to the same value (e.g., 128Mi) to guarantee QoS class Guaranteed. This prevents eviction during node contention.
- CPU Throttling: Native builds can be CPU-intensive during initialization if reflection configuration is missing. Allocate at least 500m CPU limit to prevent startup latency caused by cgroup throttling.
- Security Context: Enforce
readOnlyRootFilesystem: trueandallowPrivilegeEscalation: false. Quarkus native binaries do not need write access to the filesystem unless you are explicitly caching locally.
If you are managing secrets for database credentials or API keys referenced in these manifests, follow the patterns outlined in our guide on Kubernetes secrets management done right to avoid exposing sensitive configuration in plain text ConfigMaps.
How do you configure health checks and probes for Quarkus native binaries?
Health checks are where most deployments fail when teams attempt to run Quarkus on Kubernetes without understanding the difference between JVM and native lifecycles. In JVM mode, you might set a 30-second initial delay to allow JIT compilation and class loading. In native mode, that delay is unnecessary waste; the app is ready in 0.05 seconds or it has crashed due to missing reflection metadata.
Use the distinct endpoints provided by the quarkus-smallrye-health extension. Do not point all three probes to the same URL. Each serves a specific purpose in the orchestration logic:
- Startup Probe (/q/health/started): Use this exclusively for native apps. It tells Kubernetes "the process has initialized." Set a short period (1s) and low failure threshold. If this fails, the container is broken fundamentally.
- Readiness Probe (/q/health/ready): Indicates "I can accept traffic." This includes database connection pool validation and external service connectivity. Only after this passes should the Service endpoint add the pod.
- Liveness Probe (/q/health/live): Indicates "I am not deadlocked." Keep this simple. Do not include DB checks here; a transient network blip shouldn't restart your pod. Liveness failures trigger kills, not graceful degradation.
For deeper observability into why a probe might be failing intermittently, consider instrumenting your application early. Our article on instrumenting an app with OpenTelemetry provides the foundation for correlating health check failures with trace data.
JVM mode vs native mode: which should you choose to run Quarkus on Kubernetes?
While native is the headline feature, it isn't always the correct choice. I've audited teams who spent weeks fixing reflection errors in native mode for batch jobs that ran once daily and didn't care about startup time. Understanding the trade-offs prevents over-engineering.
| Criteria | Native Mode | JVM Mode |
|---|---|---|
| Startup Time | < 100ms (Instant) | 2–10s (Warmup required) |
| Memory Footprint | ~50–150MB RSS | ~300–600MB RSS |
| Build Time | 2–5 minutes (Heavy) | Seconds (Fast) |
| Reflection Support | Requires explicit config | Full dynamic support |
| Peak Throughput | Slightly lower (no JIT opt) | Higher (C2/Graal JIT) |
| Best Use Case | Serverless, high-density, autoscaling APIs | Long-lived monoliths, complex legacy libs |
In my experience helping Nepali fintechs and global SaaS platforms alike, the decision matrix is simple: if you are deploying to AWS Lambda, Knative, or a highly elastic EKS/GKE cluster where scale-from-zero matters, go native. If you are migrating a stable Spring Boot monolith with heavy use of dynamic proxies and don't want to rewrite integration tests, stay on JVM but use Quarkus's fast-start optimizations.
How do you manage configuration and secrets when you run Quarkus on Kubernetes?
Quarkus integrates natively with the Kubernetes API server for configuration. You don't need to mount volumes or parse files manually. By adding the quarkus-kubernetes-config extension, your application reads ConfigMaps and Secrets as property sources automatically.
# application.properties
quarkus.kubernetes-config.secrets.enabled=true
quarkus.kubernetes-config.secrets=db-credentials,api-keys
# These map directly to secret keys
%prod.quarkus.datasource.username=${DB_USER}
%prod.quarkus.datasource.password=${DB_PASS} This approach keeps your container image immutable. Never bake environment-specific config into the native binary. The binary should be identical across dev, staging, and production; only the injected configuration changes. This immutability is a core tenet of audit-ready infrastructure.
When scaling these deployments, you'll also need to consider storage if your app maintains any local state (though ideally, it shouldn't). Refer to Kubernetes persistent volumes and storage for patterns on handling stateful sets correctly if your Quarkus service requires disk-backed caches or file processing.
Deploying Quarkus on Kubernetes with Confidence
Successfully deploying Quarkus requires treating it as a distinct workload class, not just another Java jar. By adopting native compilation for latency-sensitive services, enforcing strict resource boundaries, and leveraging Kubernetes-native configuration injection, you achieve the density and responsiveness that modern platforms demand. Start with the multi-stage Dockerfile pattern above, validate your probes against actual startup metrics, and iterate based on real observability data rather than assumptions.
If your team needs assistance architecting cloud-native Java platforms or auditing existing deployments for compliance and performance, reach out to discuss your infrastructure strategy.