Run Quarkus on Kubernetes

Khimananda Oli 8 min read Programming and Languages
Run Quarkus on Kubernetes

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.

Source CodeJava / KotlinNative BuildGraalVM / MandrelUBI-Micro Image< 100MB FinalKubernetesPod / Deployment
End-to-end workflow to run Quarkus on Kubernetes: source to native binary to optimized container image to cluster deployment.

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: true and allowPrivilegeEscalation: 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.

Pod Lifecycle & Resource AllocationStartup ProbefailureThreshold: 5periodSeconds: 1Path: /q/health/startedReadiness ProbeinitialDelaySeconds: 0periodSeconds: 10Path: /q/health/readyLiveness ProbeperiodSeconds: 30timeoutSeconds: 2Path: /q/health/liveResource Guarantees (QoS: Guaranteed)Memory: 128Mi (req = limit)CPU: 500m (req = limit)
Critical probe configuration and resource guarantees required when you run Quarkus on Kubernetes to prevent restart loops.

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:

  1. 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.
  2. 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.
  3. 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.

CriteriaNative ModeJVM Mode
Startup Time< 100ms (Instant)2–10s (Warmup required)
Memory Footprint~50–150MB RSS~300–600MB RSS
Build Time2–5 minutes (Heavy)Seconds (Fast)
Reflection SupportRequires explicit configFull dynamic support
Peak ThroughputSlightly lower (no JIT opt)Higher (C2/Graal JIT)
Best Use CaseServerless, high-density, autoscaling APIsLong-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.

JVM Mode PodMemory: 450MB AvgHigh Baseline CostStartup: 4.5 SecondsSlow Scale-UpDensity: ~3 Pods / NodeStandard Java BehaviorNative Mode PodMemory: 85MB Avg5x More EfficientStartup: 0.04 SecondsInstant Scale-UpDensity: ~15 Pods / NodeTrue Cloud-Native Density
Resource comparison showing why teams run Quarkus on Kubernetes in native mode for cost savings and density.

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.

Frequently Asked Questions

Use the quarkus-kubernetes extension with mvn package to auto-generate manifests. Configure application.properties for service type, replicas, and resource limits before applying to your cluster.

No. JVM mode works fine and builds faster. Native mode reduces memory and startup time but adds CI complexity. Choose based on scaling needs and build pipeline capacity.

Use registry.access.redhat.com/ubi9/ubi-micro or eclipse-temurin:21-jre-alpine for JVM mode. These minimize attack surface and image size while maintaining glibc compatibility for native binaries in 2026.

JVM mode typically requires 256Mi to 512Mi requests. Native mode often runs comfortably with 64Mi to 128Mi. Always set limits slightly above observed peak usage to prevent OOMKills during garbage collection spikes.

Yes. The quarkus-helm extension generates charts directly from your configuration. This integrates better with GitOps workflows like ArgoCD and allows environment-specific overrides without maintaining separate YAML files manually.

Add quarkus-smallrye-health extension. It exposes /q/health/live and /q/health/ready endpoints automatically. Map these to liveness and readiness probes in your deployment spec with appropriate initial delay seconds.

Check logs with kubectl logs. Common causes include missing config maps, insufficient memory for native images, or incorrect probe paths. Verify environment variables match what application.properties expects at runtime.

Yes. Use quarkus-kubernetes-config to mount them as property sources. Values override application.properties automatically. Ensure RBAC permissions allow reading from the target namespace before deploying.

Quarkus offers faster startup and lower memory footprint, especially in native mode. Spring Boot has broader ecosystem support. For high-density clusters or serverless-style scaling, Quarkus typically delivers better resource efficiency per pod.

Use Knative if you need scale-to-zero and event-driven autoscaling. Standard Deployments suit predictable workloads requiring persistent connections or custom networking. Quarkus supports both via dedicated extensions without code changes.

Terminate TLS at the ingress controller using cert-manager. For mTLS between pods, integrate Istio or Linkerd. Quarkus handles plaintext internally; let the service mesh manage encryption and certificate rotation transparently.

Externalize env-specific values into ConfigMaps or Secrets. Use profile-aware properties in application.properties. Avoid baking environment configs into container images; inject them at deploy time via Kustomize or Helm values.

Enable remote debugging via JAVA_DEBUG=true env var and port-forward 5005. For native mode, use gdbserver. Prefer reproducing issues locally with testcontainers before attaching debuggers to production pods.

Yes. With quarkus-kubernetes-service-binding, it detects services via environment variables or mounted bindings. REST clients resolve hostnames through kube-dns without manual endpoint configuration when using standard naming conventions.

Start with 100m request and 500m limit. Native binaries are CPU-intensive only during startup. Steady-state usage is minimal. Monitor with Prometheus and adjust based on actual throttling metrics over several days.