
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Shipping cloud-native Java applications requires a pipeline that respects both startup speed and developer velocity. Implementing CI/CD for Quarkus with GitHub Actions solves the specific friction of compiling native binaries while maintaining rapid feedback loops. Unlike traditional Spring Boot workflows, Quarkus demands specialized GraalVM toolchains and intelligent caching strategies to keep build times under ten minutes. This guide provides a production-grade workflow tested across AWS EKS and Azure AKS environments.
How do you structure CI/CD for Quarkus with GitHub Actions efficiently?
Efficiency in Quarkus pipelines hinges on understanding the distinction between JVM-mode testing and native compilation. A common mistake is running native builds on every pull request; this wastes runner minutes and slows feedback. In practice, I separate validation from artifact generation. Validation runs standard JVM tests on every push, while native compilation triggers only on main branch merges or explicit tags. For teams managing broader infrastructure, aligning this with build pipeline automation best practices ensures consistency across polyglot repositories.
Your workflow file should live at .github/workflows/quarkus-ci.yml. Define environment variables centrally to avoid hardcoding versions across jobs. Always pin action versions to full commit SHAs or major version tags in production environments to prevent supply chain attacks. For Nepali teams working with limited bandwidth, consider self-hosted runners on local infrastructure to reduce data egress costs during large dependency downloads.
How do you optimize GraalVM native builds in GitHub Actions?
Native compilation is CPU-bound and memory-hungry. Without optimization, a fresh Quarkus native build can take 15–20 minutes on standard GitHub-hosted runners. The key is aggressive caching and proper JDK selection.
Selecting the right GraalVM distribution
Use the official graalvm/setup-graalvm action rather than generic Java setup actions. This configures native-image prerequisites correctly. For Quarkus 3.x in 2026, Mandrel (the Red Hat-supported GraalVM downstream) often provides better compatibility than Oracle GraalVM.
- name: Set up GraalVM
uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'mandrel'
native-image-musl: 'false'
github-token: ${{ secrets.GITHUB_TOKEN }} Caching Maven dependencies and native artifacts
Maven dependency caching alone saves 2–3 minutes. But for native builds, you must also cache the GraalVM base image layers and intermediate compilation artifacts. Configure the cache key to include both the POM hash and the GraalVM version to avoid stale binary issues.
- Dependency cache: Hash
pom.xmlfiles recursively for multi-module projects - Native cache: Include OS architecture and JDK version in the key
- Docker layer cache: Use
docker/build-push-actionbuilt-in caching for container builds
If your team handles sensitive configurations during builds, review handling secrets in CI/CD pipelines safely to ensure no credentials leak into cached layers or build logs.
What is the best way to containerize Quarkus native binaries?
You have two primary options: Jib (no Docker daemon required) or multi-stage Docker builds. Both produce minimal images, but they serve different operational needs.
| Criteria | Jib (quarkus-container-image-jib) | Multi-stage Dockerfile |
|---|---|---|
| Daemon Required | No | Yes |
| Build Speed | Faster (layer reuse) | Moderate |
| Custom Base Image | Limited | Full control |
| Security Hardening | Via extension config | Via Dockerfile directives |
| Best For | Pure CI pipelines | Compliance/custom runtimes |
For most teams, I recommend starting with Jib for its speed and simplicity. Switch to multi-stage Dockerfiles when you need custom CA certificates, specific glibc versions, or compliance-mandated base images. When deploying to Kubernetes, ensure your container strategy aligns with Kubernetes resource limits and requests since native Quarkus apps have vastly different memory profiles than JVM counterparts.
Writing a hardened multi-stage Dockerfile
When compliance requires full control over the final image, use this pattern. It separates the build environment from the runtime, includes only necessary libraries, and runs as a non-root user.
# Build stage
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
# Runtime stage
FROM quay.io/quarkus/ubi-quarkus-micro-image:2.0
COPY --from=build /code/target/*-runner /work/application
USER 1001
EXPOSE 8080
ENTRYPOINT ["/work/application", "-Dquarkus.http.host=0.0.0.0"] How do you secure deployments from GitHub Actions to AWS or Azure?
Static access keys in GitHub Secrets are a liability. In 2026, always use OpenID Connect (OIDC) federation. This grants short-lived tokens scoped to specific repositories and branches, eliminating credential rotation overhead and reducing blast radius if a secret leaks.
Configuring OIDC for AWS ECR and EKS
- Create an IAM Identity Provider for
token.actions.githubusercontent.com - Create an IAM Role with trust policy restricting
subtorepo:your-org/your-repo:ref:refs/heads/main - Attach minimal permissions: ECR push/pull, EKS describe cluster
- Use
aws-actions/configure-aws-credentials@v4withrole-to-assumeparameter
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-quarkus-deploy
aws-region: ap-south-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2 This approach mirrors the security posture discussed in deploying to AWS from GitHub Actions with OIDC. Apply identical patterns for Azure Workload Identity or GCP Workload Identity Federation depending on your cloud provider.
How do you handle testing and quality gates for Quarkus?
Quarkus introduces DevServices and continuous testing, but CI requires deterministic execution. Never rely on DevServices auto-start in CI; explicitly configure testcontainers or service dependencies via workflow services.
Integrating test coverage and mutation testing
Native builds can behave differently than JVM builds due to reflection metadata issues. Run integration tests against the native binary in a dedicated job. Use JaCoCo for coverage and consider PITest for mutation testing to validate test effectiveness.
- name: Run Native Integration Tests
run: |
./mvnw verify -Pnative \
-Dquarkus.native.container-build=true \
-Dtest.profile=native
env:
QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://localhost:5432/testdb Add SonarQube or CodeQL analysis as a blocking gate. For Quarkus specifically, enable the quarkus-smallrye-openapi extension to generate API specs automatically, then validate them against contract tests. This catches breaking changes before they reach staging.
Implementing Reliable CI/CD for Quarkus with GitHub Actions
Building production-grade CI/CD for Quarkus with GitHub Actions requires balancing native compilation performance with rigorous security controls. Start with separated JVM and native workflows, implement OIDC-based authentication from day one, and treat container image hardening as a first-class concern. Monitor your pipeline metrics: if native builds exceed eight minutes consistently, revisit your caching strategy or upgrade to larger runners. For teams needing hands-on implementation support or security review of existing pipelines, reach out directly to discuss your specific architecture.