A Fully Dockerized Gradle Workflow
In this post, I will introduce the Fully Dockerized Gradle Workflow.
Basic Optimization
Add .dockerignore
Add the .dockerignore
file to the root directory of your project.
# Gradle build output |
gradle.properties
optimization
Add the gradle.properties
file to the root directory of your project.
# Enable Configuration Cache |
build.gradle.kts
optimization
//... |
Dockerfile optimization tips
- Copy only build metadata first, not sources (layer caching). Put
settings.gradle(.kts)
,build.gradle(.kts)
,gradle.properties
,gradlew
, andgradle/
first. Run a lightweight Gradle task (that resolves dependencies) so that those downloads sit in an earlier image layer. - Use BuildKit cache mounts for Gradle caches. Reuse /home/gradle/.gradle (or whatever GRADLE_USER_HOME you set) across docker build invocations so dependency + transform caches persist outside layers.
- Only build what you need for the image. Instead of gradle build (which usually runs tests + all archives), run just the task that produces the runnable artifact, e.g. bootJar or shadowJar, and skip tests for the container image build (-x test) unless you intentionally want them there.
- Consider allowing the daemon even in Docker. If you chain multiple Gradle invocations in one RUN (e.g., prefetch + build), the daemon helps. A single invocation gains less, so –no-daemon is okay if you only call once. (Inside a disposable container there’s no “leak” concern.)
- Use a multi-stage build with a slim runtime base image. Build with a Gradle or JDK image; run on a small JRE / distroless base.
- Warm remote build cache (if you have CI). A remote build cache lets local/Docker builds pull already built class/jar outputs (after matching inputs). Requires setup (Gradle Enterprise or an OSS alternative), but can cut first build time dramatically.
Build the Docker Image
Strategy 1: Run gradle build
on the host (Recommended)
Dockerfile
FROM eclipse-temurin:21-jre-alpine AS runtime |
Or
FROM eclipse-temurin:21-jre-alpine AS runtime |
Note:
- Don’t add the
build/
directory to the.dockerignore
if you usebuild/
files in the Dockerfile. - Change the main class path
com.example.demo.DemoApplication
to your project main class.
Strategy 2: Run gradle build
in the Dockerfile (Not recommended)
1. Using the eclipse-temurin:21-alpine
JDK base image for building
FROM eclipse-temurin:21-alpine AS build |
2. Or using the gradle:9-jdk21-alpine
Gradle base image for building
FROM gradle:9-jdk21-alpine AS build |
Very slow on first builds or when file change.
- Gradle distribution / wrapper download (if you use ./gradlew and the wrapper isn’t cached yet).
- Dependency resolution & download (all your Maven / Ivy artifacts, including transitive ones).
- Creation of Gradle’s local caches (module metadata, transformed artifacts, Kotlin DSL script compilation, annotation processors, etc.).
- Compilation from scratch (no incremental / incremental annotation processing yet).
- Test execution (if you didn’t exclude tests for the image build).
- Fat / boot jar packaging (e.g., Spring Boot repackaging).
- JIT warm‑up (JVM + Kotlin/Java compilers start cold).
- No daemon: –no-daemon forces Gradle to spin up a new JVM, do all configuration, then exit. The Daemon normally amortizes startup cost across builds.
- Docker layering mistakes: copying the whole source tree before running Gradle causes every code change to invalidate the dependency download layer, so you re-download every time (feels like “always first time”).
- No (or unused) build cache: Gradle build cache and configuration cache aren’t primed yet.
Strategy 3: Using layer caching (Not recommended)
1. Using the eclipse-temurin:21-alpine
JDK base image for building
# --- Build stage --- |
2. Or using the gradle:9-jdk21-alpine
Gradle base image for building
# --- Build stage --- |
Very slow on first builds or when Gradle config files change.
Strategy 4: Reuses your host’s Gradle cache with BuildKit (Recommended)
1. Using the eclipse-temurin:21-alpine
JDK base image for building
# --- Build stage --- |
2. Or using the gradle:9-jdk21-alpine
Gradle base image for building
# --- Build stage --- |
To set the BuildKit environment variable when running the docker build
command, run:
$ DOCKER_BUILDKIT=1 docker build -t myapp . |
Or
$ export DOCKER_BUILDKIT=1 |
Using dev-mode container
dev-mode container
- Mounts your code: instant changes.
- Persistent Gradle cache: fast restarts.
1. Using the eclipse-temurin:21-alpine
JDK base image for running
docker-compose.yml
(Dev Mode)
services: |
2. Or using the gradle:9-jdk21-alpine
Gradle base image for running
services: |
Start the dev container
docker compose up -d |
Using Makefile
Using a Makefile so you can run make dev and make build instead of typing long Docker commands. It makes the workflow even cleaner.
Add the Makefile
to the root directory of your project.
Makefile
# Project name |
Usage
make help # See available commands |