Gradle on GitLab CI
This page is a reference for integrating Gradle with GitLab CI/CD on GitLab.com, self-managed, or GitLab Dedicated instances.
Using the GitLab-provided template
GitLab ships an official Gradle CI template at Gradle.gitlab-ci.yml.
Include it with:
include:
- template: Gradle.gitlab-ci.yml
The template defines three stages, build, test, deploy, using the gradle:alpine image, and configures a .gradle/ cache keyed by branch.
Use it as a starting point; most projects override the image and script sections to pin a specific JDK and use the Gradle Wrapper.
Minimal pipeline
A reference .gitlab-ci.yml that builds a Gradle project using the Wrapper and publishes test results:
image: eclipse-temurin:21
variables:
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
build:
stage: build
script:
- ./gradlew --no-daemon build
artifacts:
when: always
reports:
junit: "**/build/test-results/test/TEST-*.xml"
paths:
- "**/build/reports"
expire_in: 1 week
-
imageprovides the JDK; the Gradle Wrapper supplies Gradle itself. -
GRADLE_USER_HOMEis placed inside$CI_PROJECT_DIRso it falls within GitLab’s cacheable and uploadable area. -
GRADLE_OPTS=-Dorg.gradle.daemon=falsedisables the Gradle daemon on short-lived runners. -
reports:junitsurfaces test results directly on the merge request and pipeline pages.
Caching
GitLab caches are scoped by key and stored per runner (for project runners) or on shared object storage (for SaaS shared runners). Two patterns are common.
Wrapper-only cache
Recommended for SaaS shared runners, where uploading a large cache archive often outweighs the savings:
variables:
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
cache:
key:
files:
- gradle/wrapper/gradle-wrapper.properties
paths:
- .gradle/wrapper
Keying on gradle-wrapper.properties means the cache is rebuilt only when the Gradle version changes.
Full Gradle User Home cache
Appropriate for dedicated or self-hosted runners with local cache storage:
variables:
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/caches
- .gradle/wrapper
- .gradle/notifications
|
For cross-branch dependency reuse, prefer a Gradle Remote Build Cache. |
Cache policies across stages
For multi-stage pipelines, use the push / pull / pull-push (default) policy to control which jobs write the cache:
build:
stage: build
cache:
key: "$CI_COMMIT_REF_SLUG"
policy: pull-push
paths: [.gradle]
test:
stage: test
cache:
key: "$CI_COMMIT_REF_SLUG"
policy: pull
paths: [.gradle]
The build job populates the cache; downstream jobs read from it without uploading.
Publishing Build Scans
To publish Gradle Build Scans® from GitLab CI, apply the Develocity plugin in settings.gradle[.kts] and accept the terms of use when running under CI:
plugins {
id("com.gradle.develocity") version "3.18.1"
}
develocity {
buildScan {
if (System.getenv("CI") != null) {
termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
termsOfUseAgree = "yes"
publishing.onlyIf { true }
}
}
}
GitLab CI sets CI=true automatically.
Build Scan URLs are printed to the job log and can be linked from merge request discussions.
Merge request and branch pipelines
Use rules to distinguish merge-request pipelines from branch pipelines — for example, to skip slow integration tests on feature branches:
integration-test:
stage: test
script: ./gradlew integrationTest
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Release pipelines
A typical Gradle release pipeline on GitLab CI combines a versioning plugin (such as Axion Release) with publish targeting GitLab’s Maven Package Registry or an external repository. The pattern described in Release Gradle project using GitLab CI/CD pipeline is:
variables:
# Required so Axion Release can read existing version tags
GIT_FETCH_EXTRA_FLAGS: --tags
release:
stage: release
script:
- ./gradlew createRelease -Prelease.customUsername=$GITLAB_USER -Prelease.customPassword=$GITLAB_TOKEN
- ./gradlew publish
- echo "VERSION=$(./gradlew -q currentVersion)" >> variables.env
artifacts:
reports:
dotenv: variables.env
rules:
# Skip when the pipeline is triggered by a tag the release job itself pushed
- if: $CI_COMMIT_TAG
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Key points:
-
GIT_FETCH_EXTRA_FLAGS: --tagsexposes existing version tags to the job; without it, Axion (and similar plugins) cannot compute the next version. -
The release job needs write access to push the new tag back — supply a project access token or deploy key with
write_repositoryscope viaGITLAB_TOKEN. -
The
rulesblock prevents the tag push from triggering a recursive release. -
Credentials for publishing (for example
CI_REPOSITORY_USERNAME/CI_REPOSITORY_PASSWORD) are set as masked/protected CI/CD variables in the project settings, then read bybuild.gradle[.kts]:publishing { repositories { maven { url = uri("${System.getenv("CI_API_V4_URL")}/projects/${System.getenv("CI_PROJECT_ID")}/packages/maven") credentials(HttpHeaderCredentials::class) { name = "Job-Token" value = System.getenv("CI_JOB_TOKEN") } authentication { create<HttpHeaderAuthentication>("header") } } } }
A downstream GitLab release job can then consume the VERSION dotenv artifact to create a GitLab Release entry.
Useful predefined variables
| Variable | Purpose |
|---|---|
|
Always |
|
Absolute path to the checked-out repository. Base for |
|
URL-safe branch/tag name. Common |
|
Populated on branch and tag pipelines respectively. Use in |
|
Trigger type ( |
|
Short-lived token for authenticating to the project’s package registry and API. |
|
Compose URLs to the GitLab API and package registries. |
See Predefined CI/CD variables for the full list.