Quarkus Deployment Guide
Status: Final
Version: 1.0
Purpose
This guide provides comprehensive instructions for deploying the ESG Reporting Platform using Quarkus in both JVM and native modes.
Table of Contents
- Packaging Options
- Docker Images
- Kubernetes Deployment
- Health Checks
- Resource Requirements
- Native Image Compilation
- Development Features
- Production Checklist
Packaging Options
Quarkus provides multiple packaging formats, each optimized for different deployment scenarios.
Fast-Jar (Default)
The default packaging mode for Quarkus, optimized for fast startup and low memory usage.
Output: target/quarkus-app/
Structure:
quarkus-app/
├── app/
│ └── esg-platform-1.0.0.jar # Application code
├── lib/
│ └── *.jar # Dependencies
├── quarkus/
│ └── generated-bytecode.jar # Quarkus-generated code
└── quarkus-run.jar # Main JAR
Run:
Advantages: - Fast startup (~1 second) - Low memory usage (~70-100 MB) - Efficient for containers (layered for caching)
Uber-Jar
Single JAR containing all dependencies, useful for traditional deployment.
Output: target/esg-platform-1.0.0-runner.jar
Run:
Advantages: - Single file deployment - Traditional deployment model - Easier to distribute
Disadvantages: - Slower startup vs fast-jar - Larger file size - No Docker layer caching benefits
Native Executable
Compiled to native code using GraalVM, providing the fastest startup and lowest memory footprint.
Output: target/esg-platform-1.0.0-runner
Run:
Advantages: - Extremely fast startup (< 100ms) - Minimal memory usage (< 50 MB) - Instant scale-up for serverless/auto-scaling - Reduced attack surface
Disadvantages: - Longer build time (3-5 minutes) - Requires GraalVM or Docker - Some libraries require additional configuration
Docker Images
JVM Mode Dockerfile
Multi-stage build for optimal JVM container image:
# Build stage
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# Runtime stage
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 quarkus && \
adduser -u 1001 -G quarkus -D -h /app quarkus
# Copy application
COPY --from=build --chown=quarkus:quarkus /build/target/quarkus-app /app
# Switch to non-root user
USER quarkus
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/q/health/live || exit 1
# Expose port
EXPOSE 8080
# Run application
CMD ["java", "-jar", "/app/quarkus-run.jar"]
Build and run:
Image size: ~200-250 MB (with Alpine JRE)
Native Mode Dockerfile
Multi-stage build for native executable:
# Build stage with GraalVM
FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21 AS build
USER quarkus
WORKDIR /build
COPY --chown=quarkus:quarkus pom.xml .
COPY --chown=quarkus:quarkus src ./src
RUN mvn clean package -Pnative -DskipTests
# Runtime stage - minimal image
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /app
# Copy native executable
COPY --from=build --chown=1001:1001 /build/target/*-runner /app/application
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/q/health/live || exit 1
# Expose port
EXPOSE 8080
# Run application
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
Build and run:
docker build -f Dockerfile.native -t esg-platform:native .
docker run -p 8080:8080 esg-platform:native
Image size: ~50-80 MB
Quarkus Docker Extension
Alternatively, use Quarkus to generate Dockerfiles automatically:
Configuration in application.properties:
# Docker image configuration
quarkus.container-image.build=true
quarkus.container-image.group=esg-platform
quarkus.container-image.name=api
quarkus.container-image.tag=latest
# Build JVM image
quarkus.container-image.builder=docker
# Or build native image
#quarkus.native.container-build=true
Build image:
Kubernetes Deployment
Deployment Manifest (JVM Mode)
apiVersion: apps/v1
kind: Deployment
metadata:
name: esg-platform
labels:
app: esg-platform
mode: jvm
spec:
replicas: 3
selector:
matchLabels:
app: esg-platform
template:
metadata:
labels:
app: esg-platform
mode: jvm
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: esg-platform
image: esg-platform:jvm
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
# Quarkus health checks
livenessProbe:
httpGet:
path: /q/health/live
port: 8080
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /q/health/ready
port: 8080
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /q/health/started
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 12
# Resource limits for JVM mode
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: QUARKUS_PROFILE
value: "prod"
- name: QUARKUS_DATASOURCE_JDBC_URL
valueFrom:
secretKeyRef:
name: esg-database
key: jdbc-url
- name: QUARKUS_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: esg-database
key: username
- name: QUARKUS_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: esg-database
key: password
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
---
apiVersion: v1
kind: Service
metadata:
name: esg-platform
labels:
app: esg-platform
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: esg-platform
Deployment Manifest (Native Mode)
For native mode, adjust resource requirements:
# In Deployment spec.template.spec.containers[0].resources
resources:
requests:
memory: "64Mi" # Much lower memory requirements
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
# Faster startup probe
startupProbe:
httpGet:
path: /q/health/started
port: 8080
initialDelaySeconds: 1 # Native starts almost instantly
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 5
Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: esg-platform-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: esg-platform
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
selectPolicy: Max
Generate Kubernetes Resources with Quarkus
Quarkus can generate Kubernetes manifests automatically:
Configuration in application.properties:
# Kubernetes resource generation
quarkus.kubernetes.deployment-target=kubernetes
quarkus.kubernetes.replicas=3
# Health checks
quarkus.kubernetes.liveness-probe.http-action-path=/q/health/live
quarkus.kubernetes.readiness-probe.http-action-path=/q/health/ready
# Resources
quarkus.kubernetes.resources.requests.memory=256Mi
quarkus.kubernetes.resources.requests.cpu=250m
quarkus.kubernetes.resources.limits.memory=512Mi
quarkus.kubernetes.resources.limits.cpu=500m
# Labels
quarkus.kubernetes.labels."app.kubernetes.io/name"=esg-platform
quarkus.kubernetes.labels."app.kubernetes.io/version"=1.0.0
Generate manifests:
Health Checks
Quarkus provides MicroProfile Health endpoints for liveness, readiness, and startup checks.
Default Health Endpoints
| Endpoint | Purpose | When to Use |
|---|---|---|
/q/health |
Combined health status | Overall health check |
/q/health/live |
Liveness probe | Detect broken application state |
/q/health/ready |
Readiness probe | Check if app can accept traffic |
/q/health/started |
Startup probe | Check if app has started |
Add Health Extension
Custom Health Checks
Liveness check (detects broken application state):
import org.eclipse.microprofile.health.HealthCheck
import org.eclipse.microprofile.health.HealthCheckResponse
import org.eclipse.microprofile.health.Liveness
import jakarta.enterprise.context.ApplicationScoped
@Liveness
@ApplicationScoped
class DatabaseConnectionHealthCheck : HealthCheck {
@Inject
lateinit var dataSource: DataSource
override fun call(): HealthCheckResponse {
return try {
dataSource.connection.use { conn ->
val isValid = conn.isValid(5)
HealthCheckResponse.named("database-connection")
.status(isValid)
.withData("connection_pool_active", "true")
.build()
}
} catch (e: Exception) {
HealthCheckResponse.named("database-connection")
.down()
.withData("error", e.message)
.build()
}
}
}
Readiness check (checks dependencies):
import org.eclipse.microprofile.health.Readiness
@Readiness
@ApplicationScoped
class ExternalServicesHealthCheck : HealthCheck {
@Inject
@RestClient
lateinit var reportingServiceClient: ReportingServiceClient
@Inject
lateinit var redisClient: RedisDataSource
override fun call(): HealthCheckResponse {
val builder = HealthCheckResponse.named("external-services")
try {
// Check Redis
redisClient.value(String::class.java).ping()
builder.withData("redis", "up")
// Check reporting service
reportingServiceClient.healthCheck()
builder.withData("reporting-service", "up")
builder.up()
} catch (e: Exception) {
builder.down().withData("error", e.message)
}
return builder.build()
}
}
Startup check (warm-up tasks):
import org.eclipse.microprofile.health.Startup
@Startup
@ApplicationScoped
class CacheWarmupHealthCheck : HealthCheck {
@Inject
lateinit var cacheService: CacheWarmupService
override fun call(): HealthCheckResponse {
val isWarmedUp = cacheService.isInitialized()
return HealthCheckResponse.named("cache-warmup")
.status(isWarmedUp)
.withData("cache_entries", cacheService.getEntryCount().toString())
.build()
}
}
Health Check Response Example
{
"status": "UP",
"checks": [
{
"name": "database-connection",
"status": "UP",
"data": {
"connection_pool_active": "true"
}
},
{
"name": "external-services",
"status": "UP",
"data": {
"redis": "up",
"reporting-service": "up"
}
}
]
}
Resource Requirements
JVM Mode
Minimum resources:
Recommended for production:
Characteristics: - Startup time: 1-3 seconds - Base memory: ~100-150 MB - Memory under load: 200-400 MB - Warm-up time: 5-10 seconds
Native Mode
Minimum resources:
Recommended for production:
Characteristics: - Startup time: < 100ms - Base memory: ~30-50 MB - Memory under load: 80-150 MB - Warm-up time: Instant (no JIT)
Comparison Table
| Metric | JVM Mode | Native Mode | Improvement |
|---|---|---|---|
| Startup Time | 1-3 seconds | < 100ms | 30x faster |
| Memory (Base) | 150 MB | 40 MB | 3.7x less |
| Memory (Load) | 300 MB | 100 MB | 3x less |
| Image Size | 250 MB | 60 MB | 4x smaller |
| Time to First Request | 5 seconds | Instant | Immediate |
| Build Time | 30 seconds | 4 minutes | 8x slower |
| Peak Throughput | High | Slightly lower | JVM JIT optimizes |
Cost Savings Example
Scenario: 50 pods running 24/7
| Mode | Memory per Pod | Total Memory | Monthly Cost (AWS EKS)* |
|---|---|---|---|
| JVM | 512 MB | 25.6 GB | ~$80 |
| Native | 128 MB | 6.4 GB | ~$20 |
*Approximate costs, actual pricing varies
Annual savings with native mode: ~$720
Native Image Compilation
Prerequisites
Option 1: Install GraalVM locally
# Download GraalVM
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.1/graalvm-community-jdk-21.0.1_linux-x64_bin.tar.gz
tar -xzf graalvm-community-jdk-21.0.1_linux-x64_bin.tar.gz
export GRAALVM_HOME=$PWD/graalvm-community-openjdk-21.0.1+12.1
export PATH=$GRAALVM_HOME/bin:$PATH
Option 2: Use Docker (recommended)
Build Native Executable
Maven:
# Local build (requires GraalVM)
./mvnw clean package -Pnative
# Container build (uses Docker)
./mvnw clean package -Pnative -Dquarkus.native.container-build=true
# With additional native-image options
./mvnw clean package -Pnative \
-Dquarkus.native.additional-build-args="--verbose,-H:+ReportExceptionStackTraces"
Gradle:
./gradlew build -Dquarkus.package.type=native
# Container build
./gradlew build -Dquarkus.package.type=native \
-Dquarkus.native.container-build=true
Native Configuration
application.properties:
# Native image configuration
quarkus.native.resources.includes=META-INF/**,templates/**,reports/**
quarkus.native.enable-http-url-handler=true
quarkus.native.enable-https-url-handler=true
# Reflection configuration (auto-detected by Quarkus)
# Add manual entries if needed:
quarkus.native.additional-build-args=\
--initialize-at-run-time=io.netty.handler.ssl.ReferenceCountedOpenSslEngine
# Memory settings for native-image build
quarkus.native.native-image-xmx=6g
Reflection Configuration
Quarkus automatically registers most reflection needs, but you can add custom entries:
src/main/resources/reflection-config.json:
[
{
"name": "com.example.esg.model.MetricSubmission",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredFields": true,
"allPublicFields": true
}
]
Testing Native Build
# Build native image
./mvnw clean package -Pnative -Dquarkus.native.container-build=true
# Run native tests
./mvnw verify -Pnative
# Run native executable
./target/esg-platform-1.0.0-runner
Troubleshooting Native Builds
Issue: ClassNotFoundException at runtime
# Add reflection configuration
quarkus.native.additional-build-args=\
-H:ReflectionConfigurationFiles=reflection-config.json
Issue: Resource not found
Issue: Out of memory during build
Issue: SSL errors
Development Features
Dev Mode
Quarkus provides continuous testing and live reload during development.
Start dev mode:
Features:
- Hot reload (automatic code recompilation)
- Live reload (browser auto-refresh)
- Dev UI at http://localhost:8080/q/dev
- Continuous testing
- Dev Services (automatic test containers)
Dev UI Features: - Configuration browser - Extension list - Health check viewer - OpenAPI/Swagger UI - Database console (H2) - Arc (CDI) bean inspector - Scheduler viewer
Continuous Testing
In dev mode, tests run automatically on code changes:
# Start dev mode with continuous testing
./mvnw quarkus:dev
# In dev mode console:
# Press 'r' - Re-run tests
# Press 'f' - Run failed tests only
# Press 'b' - Toggle breakpoints
# Press 'i' - Toggle instrumentation
# Press 's' - Open test report in browser
Enable continuous testing by default:
Dev Services
Quarkus automatically starts test containers for databases, message brokers, etc.
Automatic services: - PostgreSQL - MySQL - MongoDB - Kafka - Redis - Keycloak
Configuration:
# Dev Services PostgreSQL (automatic)
%dev.quarkus.datasource.db-kind=postgresql
# No URL needed - DevServices starts container automatically
# Customize DevServices
%dev.quarkus.datasource.devservices.image-name=postgres:16
%dev.quarkus.datasource.devservices.port=5432
View DevServices:
Remote Dev Mode
Develop against a remote Quarkus application:
# Package for remote dev
./mvnw clean package -Dquarkus.package.type=mutable-jar
# Start app with remote dev enabled
java -Dquarkus.live-reload.password=secret \
-jar target/quarkus-app/quarkus-run.jar
# Connect from local
./mvnw quarkus:remote-dev -Dquarkus.live-reload.url=http://remote:8080 \
-Dquarkus.live-reload.password=secret
Production Checklist
Pre-Deployment
- Run full test suite:
./mvnw verify - Build optimized package:
./mvnw clean package -Dquarkus.profile=prod - Scan image for vulnerabilities:
docker scan esg-platform:latest - Review resource limits in Kubernetes manifests
- Configure production datasource URL and credentials
- Set up secrets management (HashiCorp Vault, AWS Secrets Manager)
- Enable TLS/SSL for external connections
- Configure CORS policy for production domains
- Review and enable security headers
- Set up log aggregation (ELK, Loki, CloudWatch)
- Configure metrics collection (Prometheus)
- Set up distributed tracing (Jaeger, Tempo)
- Define alerting rules (Prometheus Alertmanager)
- Configure horizontal pod autoscaling
- Set up network policies
- Enable pod security standards
- Configure backup and disaster recovery
- Document runbook procedures
Production Configuration
application.properties (%prod profile):
# Production profile
%prod.quarkus.http.port=8080
%prod.quarkus.http.host=0.0.0.0
# Security
%prod.quarkus.http.cors=true
%prod.quarkus.http.cors.origins=https://esg-platform.example.com
%prod.quarkus.http.ssl.port=8443
%prod.quarkus.http.ssl.certificate.key-store-file=/etc/ssl/keystore.p12
%prod.quarkus.http.ssl.certificate.key-store-password=${SSL_KEYSTORE_PASSWORD}
# Database
%prod.quarkus.datasource.jdbc.url=${QUARKUS_DATASOURCE_JDBC_URL}
%prod.quarkus.datasource.username=${QUARKUS_DATASOURCE_USERNAME}
%prod.quarkus.datasource.password=${QUARKUS_DATASOURCE_PASSWORD}
%prod.quarkus.datasource.jdbc.max-size=20
%prod.quarkus.datasource.jdbc.min-size=5
# Hibernate
%prod.quarkus.hibernate-orm.database.generation=none
%prod.quarkus.hibernate-orm.sql-load-script=no-file
# Logging
%prod.quarkus.log.level=INFO
%prod.quarkus.log.category."com.example.esg".level=INFO
%prod.quarkus.log.console.json=true
# Metrics
%prod.quarkus.micrometer.enabled=true
%prod.quarkus.micrometer.export.prometheus.enabled=true
%prod.quarkus.micrometer.binder.http-server.enabled=true
%prod.quarkus.micrometer.binder.jvm.enabled=true
# Tracing
%prod.quarkus.opentelemetry.enabled=true
%prod.quarkus.opentelemetry.tracer.exporter.otlp.endpoint=http://jaeger:4317
# Health checks
%prod.quarkus.smallrye-health.ui.enable=false
Deployment Commands
JVM Mode:
# Build
./mvnw clean package -Dquarkus.profile=prod
# Build Docker image
docker build -f Dockerfile.jvm -t esg-platform:1.0.0-jvm .
# Push to registry
docker tag esg-platform:1.0.0-jvm your-registry/esg-platform:1.0.0-jvm
docker push your-registry/esg-platform:1.0.0-jvm
# Deploy to Kubernetes
kubectl apply -f k8s/deployment-jvm.yaml
kubectl rollout status deployment/esg-platform
Native Mode:
# Build native image
./mvnw clean package -Pnative -Dquarkus.native.container-build=true
# Build Docker image
docker build -f Dockerfile.native -t esg-platform:1.0.0-native .
# Push to registry
docker tag esg-platform:1.0.0-native your-registry/esg-platform:1.0.0-native
docker push your-registry/esg-platform:1.0.0-native
# Deploy to Kubernetes
kubectl apply -f k8s/deployment-native.yaml
kubectl rollout status deployment/esg-platform
Post-Deployment Verification
# Check pod status
kubectl get pods -l app=esg-platform
# Check health endpoints
kubectl port-forward svc/esg-platform 8080:80
curl http://localhost:8080/q/health
curl http://localhost:8080/q/health/live
curl http://localhost:8080/q/health/ready
# View logs
kubectl logs -l app=esg-platform --tail=100 -f
# Check metrics
curl http://localhost:8080/q/metrics
# View OpenAPI spec
curl http://localhost:8080/q/openapi
Monitoring Queries
Prometheus queries:
# Request rate
rate(http_server_requests_seconds_count[5m])
# Error rate
rate(http_server_requests_seconds_count{status=~"5.."}[5m])
# P95 latency
histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m]))
# Memory usage
process_resident_memory_bytes / 1024 / 1024
# JVM heap (JVM mode only)
jvm_memory_used_bytes{area="heap"} / 1024 / 1024
Cross-References
- Non-Functional Requirements
- Testing Strategy
- Security Compliance
- Quarkus Security RBAC Implementation
Additional Resources
- Quarkus Container Images Guide
- Quarkus Kubernetes Guide
- Quarkus Native Guide
- Quarkus Health Guide
- GraalVM Native Image Documentation
Change Log
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-13 | Ralph Agent | Initial Quarkus deployment guide |