CI/CD for Quarkus with GitHub Actions

Khimananda Oli 7 min read Programming and Languages
CI/CD for Quarkus with GitHub Actions

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.

Git PushJVM Test & SAST(Every PR / Push)Native Compile(Main / Tag Only)CodeQL ScanContainer Build(Docker / Jib)Deploy K8s
Optimized CI/CD for Quarkus with GitHub Actions separating fast JVM validation from resource-intensive native builds

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.xml files recursively for multi-module projects
  • Native cache: Include OS architecture and JDK version in the key
  • Docker layer cache: Use docker/build-push-action built-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.

CriteriaJib (quarkus-container-image-jib)Multi-stage Dockerfile
Daemon RequiredNoYes
Build SpeedFaster (layer reuse)Moderate
Custom Base ImageLimitedFull control
Security HardeningVia extension configVia Dockerfile directives
Best ForPure CI pipelinesCompliance/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.

Native BinaryJib ExtensionNo Daemon • Fast LayersDocker Multi-stageCustom Base • Full ControlRegistry PushTrivy ScanSign
Container build paths for Quarkus native binaries with integrated security scanning

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

  1. Create an IAM Identity Provider for token.actions.githubusercontent.com
  2. Create an IAM Role with trust policy restricting sub to repo:your-org/your-repo:ref:refs/heads/main
  3. Attach minimal permissions: ECR push/pull, EKS describe cluster
  4. Use aws-actions/configure-aws-credentials@v4 with role-to-assume parameter
- 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.

Unit TestsIntegration(DevServices)Native IT(Container)SonarQubeCoverage GatePassPostgreSQL ServiceTestcontainers
Sequential quality gates ensuring native binary correctness before deployment approval

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.

Frequently Asked Questions

Use the graalvm/setup-graalvm action with distribution set to mandrel and java-version matching your project. Add a build step running mvn package -Pnative to compile the executable, then upload the binary as an artifact for downstream deployment stages in your workflow.

Red Hat UBI Micro or Eclipse Temurin Alpine are preferred for minimal attack surface and size. Both support static linking required by Mandrel 23.1+. Avoid full JDK images since native binaries only need glibc or musl libraries at runtime, reducing container size significantly.

Most failures stem from missing GraalVM version alignment or insufficient runner memory. Ensure your pom.xml specifies the exact Mandrel version matching setup-graalvm inputs. Increase runner RAM using larger GitHub-hosted runners or self-hosted options if OOM errors occur during native-image compilation phase.

Optimized pipelines complete native builds in eight to twelve minutes using GitHub-hosted ubuntu-latest-xlarge runners. Caching Maven dependencies and GraalVM installations reduces overhead. Parallel test execution and skipping unnecessary JVM tests before native compilation further cuts total pipeline duration without sacrificing coverage validation.

Yes.

Configure the failsafe plugin with @QuarkusIntegrationTest annotations. In your workflow, start dependent services like PostgreSQL via Docker Compose before running mvn verify -Pnative. Set quarkus.test.wait-timeout appropriately and use service containers defined in the job to ensure database readiness during native test execution.

Store credentials in GitHub Encrypted Secrets and inject them as environment variables during build steps. Never embed secrets in application.properties. Use Quarkus config sources like ${ENV_VAR} syntax to reference injected values. Rotate secrets regularly and restrict access using GitHub repository environments with required reviewers for production deployments.

No.

Build the container image using docker/build-push-action after native compilation. Push to GHCR or ECR, then update your Helm chart or Kustomize manifests with the new tag. Apply changes using azure/k8s-deploy or argocd CLI. Ensure health endpoints match liveness probe configurations for fast startup times.

Define separate profiles for jvm-tests, native-build, and native-tests. The native-build profile activates -Pnative and skips unit tests already validated earlier. Native-tests profile runs integration tests against the compiled binary. This separation allows selective execution in different workflow jobs, optimizing resource usage and feedback speed.

Use concurrency groups to cancel redundant runs on the same branch. Cache aggressively with actions/cache for Maven and GraalVM. Run native builds only on main merges, not PRs. Consider ARM-based runners which offer better price-performance for native compilation workloads compared to x86 equivalents in 2026.

Yes, but requires Docker-in-Docker or service containers. Dev Services automatically starts test dependencies when running mvn test or verify. For native tests, explicitly define service containers in your workflow YAML since Dev Services may not initialize correctly inside restricted CI environments without proper socket mounting configuration.

Use docker/setup-qemu-action and docker/buildx-action to cross-compile. Build separate native binaries for amd64 and arm64 using matrix strategy with distinct GraalVM setups. Create manifest lists combining both architectures. Note that true native cross-compilation requires architecture-matched runners; emulation adds significant build time overhead.

Validate OpenTelemetry endpoint availability and metric exposition format during integration tests. Assert health check responses include expected subsystem statuses. Verify log output conforms to structured JSON schema. Fail builds if critical telemetry signals are missing or malformed, preventing unobservable deployments from reaching staging or production environments.

Enable verbose logging with -X flag to identify bottleneck phases. Check runner specs match workload requirements. Profile build time per module using Maven timeline reports. Verify cache hit rates for dependencies and GraalVM. Consider splitting monorepo builds into parallel jobs targeting individual modules to isolate performance regressions.