Deploy Quarkus to Production: A Practical Guide

Khimananda Oli 8 min read Programming and Languages
Deploy Quarkus to Production: A Practical Guide

By Khimananda Oli | Last reviewed: August 2026

Moving a Java application from development to a live environment requires more than just copying a JAR file; you must optimize for startup latency, memory footprint, and security compliance. This is especially true when you deploy Quarkus to production, where the framework’s supersonic subatomic capabilities demand specific build-time configurations to avoid runtime failures. Unlike traditional Spring Boot applications that rely heavily on reflection at runtime, Quarkus shifts this work to the build phase, meaning your production artifact is fundamentally different from your development binary. Getting this transition right ensures you actually realize the promised benefits of low memory usage and instant scaling in cloud-native environments.

Source CodeJava / KotlinNative BuildGraalVM / MandrelContainer ImageDistroless / UBIKubernetesEKS / GKE / AKSFigure 1: End-to-end pipeline to deploy Quarkus to production securely
End-to-end pipeline to deploy Quarkus to production securely

How do you build a native Quarkus image for production?

The most common mistake engineers make when they first deploy Quarkus to production is attempting to run the standard JVM-mode JAR in environments optimized for high density. While JVM mode works, native compilation is where Quarkus shines, reducing memory consumption by up to 80% and startup times to milliseconds. You should use the official Red Hat Mandrel or Oracle GraalVM distribution for reproducible builds.

Configuring the Native Build Profile

In your pom.xml or build.gradle, ensure the native profile includes essential flags for production readiness. Do not rely on defaults, as they often disable features required for real-world traffic like SSL and timezone data.

<properties>
    <quarkus.native.additional-build-args>
        --initialize-at-run-time=io.netty.handler.ssl.BouncyCastleAlpnSslUtils,
        -H:+AddAllCharsets,
        -H:IncludeResourceBundles=com.sun.org.apache.xerces.internal.impl.msg.XMLMessages
    </quarkus.native.additional-build-args>
</properties>

Run the build using Maven or Gradle with the native profile active. Note that native compilation is CPU-intensive and can take several minutes depending on your hardware. In CI pipelines, allocate at least 4 vCPUs and 8GB RAM to the build job to prevent out-of-memory errors during the linking phase.

./mvnw package -Pnative -DskipTests \
  -Dquarkus.native.container-build=true \
  -Dquarkus.container-image.build=true

The flag -Dquarkus.native.container-build=true is critical. It instructs Quarkus to perform the native compilation inside a Linux container rather than on your host machine. This guarantees binary compatibility with your target production base image and eliminates "GLIBC version mismatch" errors that plague developers building on macOS or Windows.

What is the best Dockerfile strategy for Quarkus native binaries?

When you deploy Quarkus to production, your container image size directly impacts deployment speed, autoscaling responsiveness, and attack surface. Avoid using full JDK base images for native executables. Instead, adopt a multi-stage build pattern that separates the heavy compilation environment from the lean runtime environment.

Stage 1: Buildermandrel-java21-rhel8Size: ~1.8 GBGraalVM + Maven + SourceNative Compilation OutputCOPY ONLYBINARYStage 2: Runtimegcr.io/distroless/baseSize: ~80 MBNative Executable OnlyNo Shell / No Package MgrFigure 2: Multi-stage build reduces attack surface when you deploy Quarkus to production
Multi-stage build reduces attack surface when you deploy Quarkus to production

Implementing Secure Multi-Stage Builds

The following Dockerfile demonstrates the gold standard for Quarkus native deployments in 2026. It uses a distroless base image, which contains no shell, no package manager, and no unnecessary libraries. This makes vulnerability scanning cleaner and prevents attackers from executing arbitrary commands if they compromise the application.

# 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 gcr.io/distroless/base-debian12
WORKDIR /work/
COPY --from=build /code/target/*-runner /work/application
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

Notice the USER nonroot:nonroot directive. Running containers as root is a critical security violation in any SOC 2 or ISO 27001 audit. Distroless images provide a built-in non-root user specifically for this purpose. Also note that we pass -Dquarkus.http.host=0.0.0.0 at runtime; without this, Quarkus binds to localhost only and will fail health checks in Kubernetes.

How do you configure Kubernetes probes for Quarkus?

Quarkus starts so quickly that default Kubernetes probe settings often cause unnecessary restarts. When you deploy Quarkus to production on platforms like Amazon EKS or GKE, you must tune your liveness and readiness probes to match the framework's actual performance characteristics rather than legacy Java assumptions.

Optimizing Health Check Endpoints

Quarkus exposes dedicated health endpoints via the SmallRye Health extension. Add the dependency to your project:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

This provides /q/health/live and /q/health/ready endpoints. Configure your Kubernetes deployment manifest with aggressive but safe timing:

  • Liveness Probe: Set initialDelaySeconds: 1 and periodSeconds: 10. Native Quarkus apps are typically live within 50ms.
  • Readiness Probe: Set initialDelaySeconds: 0 and failureThreshold: 3. This allows immediate traffic routing once the app signals ready.
  • Startup Probe: Optional for native, but useful if you have slow database migrations. Set failureThreshold: 30 with periodSeconds: 1.

A common pitfall is setting resource limits too high. Because native Quarkus uses significantly less heap memory, allocating 2Gi RAM wastes cluster resources. Start with 128Mi–256Mi requests and monitor actual usage with Prometheus metrics before adjusting upward.

JVM mode vs native mode: which should you choose for production?

While native mode is the headline feature, it is not universally superior. Understanding the trade-offs prevents costly rework when you deploy Quarkus to production for complex enterprise workloads. The decision matrix below reflects real-world operational experience across dozens of microservices.

CriteriaNative ModeJVM Mode
Startup Time~50ms (Excellent for serverless)~2-5s (Acceptable for long-running)
Memory Footprint~80-150MB RSS~300-600MB RSS
Build Time3-10 minutes (CPU intensive)~30 seconds
Reflection SupportLimited (requires registration)Full dynamic reflection
Library CompatibilitySome libs need GraalVM patchesUniversal Java compatibility
Debugging ComplexityHigher (no JIT, limited tooling)Standard JFR/JMX tools
Best Use CaseHigh-density, auto-scaling APIsComplex monoliths, heavy reflection

If your application relies heavily on dynamic proxies, Groovy scripting, or legacy libraries that perform unchecked reflection, start with JVM mode. You can always migrate to native later after registering necessary classes. For new microservices designed with Quarkus extensions, native mode delivers tangible cost savings in cloud bills due to reduced compute requirements.

Start AssessmentHeavy Reflection / Legacy Libs?YESNOUse JVM ModeConsider NativeFaster Dev CyclesLower Cloud CostsFigure 3: Decision criteria when you deploy Quarkus to production in 2026
Decision criteria when you deploy Quarkus to production in 2026

How do you manage secrets and configuration securely?

Never bake credentials into your native executable or container image. Quarkus supports runtime configuration overrides via environment variables and mounted files, which is essential for compliance when you deploy Quarkus to production. Use the ${ENV_VAR::default} syntax in application.properties to enable flexible injection without recompilation.

For sensitive values like database passwords or API keys, integrate with Kubernetes Secrets or HashiCorp Vault. Mount secrets as files rather than environment variables when possible, as env vars can leak in logs, crash dumps, and process listings. Quarkus automatically reads properties from mounted secret volumes when configured with the appropriate property source.

# application.properties example
quarkus.datasource.jdbc.url=${DB_URL::jdbc:postgresql://localhost:5432/mydb}
quarkus.datasource.username=${DB_USER::appuser}
quarkus.datasource.password=${file:/secrets/db-password}

This approach ensures your CI-built artifact remains identical across staging and production environments. Only the injected configuration changes, satisfying the twelve-factor app methodology and simplifying rollback procedures during incidents.

Deploy Quarkus to Production: Final Checklist and Next Steps

Successfully operating Quarkus in live environments requires discipline beyond the initial setup. Before marking your deployment complete, verify that you have implemented structured logging compatible with your observability stack, configured proper resource limits based on actual native memory profiles, and established automated security scanning for your container images. Test your native build thoroughly in a staging environment that mirrors production infrastructure, as subtle differences in glibc versions or kernel parameters can surface only under load.

Remember that the goal when you deploy Quarkus to production is sustainable velocity, not just raw performance metrics. Monitor your error budgets and adjust probe timings based on real latency percentiles rather than theoretical benchmarks. If you need assistance architecting a compliant, high-performance Java platform or auditing your existing Quarkus deployment for security and efficiency gaps, reach out to discuss your infrastructure needs. Proper foundational work now prevents expensive rewrites and outage-driven fire drills later.

Frequently Asked Questions

Use Eclipse Temurin JDK 21 or later. Quarkus 3.x requires Java 17 minimum, but JDK 21 provides virtual threads and improved garbage collection performance essential for high-throughput production workloads without native compilation overhead.

Enable the quarkus-smallrye-health extension. Expose /q/health/live for liveness and /q/health/ready for readiness probes. Configure initialDelaySeconds to prevent premature restarts during startup, especially for JVM mode containers requiring warmup time.

No. Native images reduce memory and startup time but increase build complexity and lose some reflection capabilities. JVM mode with CDS archives often delivers sufficient performance with easier debugging and full Java ecosystem compatibility for most production services.

Use Vault, AWS Secrets Manager, or Kubernetes Secrets with external-secrets operator. Never embed credentials in application.properties. Configure Quarkus credentials provider to fetch secrets at runtime with automatic rotation support and audit logging enabled.

Use ubi-micro or distroless images for native builds. For JVM mode, use eclipse-temurin:21-jre-alpine. Both eliminate shells and package managers, reducing CVE exposure while maintaining compatibility with Quarkus runtime requirements and security scanning tools.

Set quarkus.shutdown.timeout=30s in application.properties. This allows active HTTP requests and message consumers to complete before termination. Combine with Kubernetes preStop hooks and SIGTERM handling to prevent dropped connections during rolling updates.

Partially. Quarkus supports @ConfigMapping and CDI injection similar to Spring, but lacks runtime bean creation. Migrate spring-boot-starter dependencies to Quarkus equivalents. Use quarkus-spring-di for limited compatibility, but prefer native Quarkus extensions for production stability.

Set -XX:MaxRAMPercentage=75.0 and -XX:InitialRAMPercentage=50.0 instead of fixed heap sizes. This respects container cgroup limits dynamically. Monitor with jcmd GC.heap_info and adjust based on actual RSS usage patterns under load testing.

Use OpenTelemetry with quarkus-opentelemetry extension. Export traces to Tempo, metrics to Prometheus, and logs to Loki. Avoid vendor-specific agents. Configure W3C trace context propagation for distributed tracing across microservices and external API calls.

Configure Agroal pool via quarkus.datasource.jdbc.max-size matching your database max_connections divided by replica count. Enable leak detection with quarkus.datasource.jdbc.leak-detection-interval=2m. Monitor active vs idle connections through Micrometer metrics to prevent exhaustion.

Yes, with proper health checks and graceful shutdown configured. Use Kubernetes rolling update strategy with maxSurge=1 and maxUnavailable=0. Ensure idempotent endpoints and database migrations run separately from application startup to prevent schema conflicts during transitions.

Use async-profiler via quarkus-profiler extension or JFR streaming. Avoid synchronous profilers causing latency spikes. Configure sampling intervals above 10ms and limit duration to five-minute windows during low-traffic periods to minimize performance impact.

Classloading overhead and bean initialization dominate cold starts. Enable AppCDS with quarkus.package.jar.appcds=true to cache class metadata. Pre-warm caches via /q/warmup endpoint after readiness. Consider tiered compilation flags to balance startup versus peak throughput.

Use %prod profile in application.properties with identical values to deployed environment. Run podman or docker with same resource limits and environment variables. Validate with quarkus:test integration tests against testcontainers mirroring production infrastructure topology.

Choose serverless for event-driven, sporadic workloads with sub-second cold start tolerance. Use containers for sustained traffic, stateful services, or custom networking. Quarkus native mode bridges both, but Knative or Lambda adds operational complexity unsuitable for all use cases.