Reuse Gradle results in Docker CI, even in fresh containers
A new container with an empty Gradle home can take its compile result from the cache, so containers that keep nothing still skip work. At FosterStack we prove everything, so here is how we proved it to ourselves: real Linux container runs, a different mount path, and a missing password.
The short answer
- A new container with an empty Gradle home restored the compile result from the cache, even from a different folder path.
- A container started without the password still built, but ran without the cache and printed a 401 line.
- Inside one container, a password set after Gradle's background process had started was used by the next build.
What we ran
The cache server ran on a Mac, with a username and password. Gradle ran in the gradle:9.8.0-jdk21 image (Linux, aarch64, Java 21) under Docker Desktop on that same Mac. Every build was a new docker run --rm with no volumes for the Gradle home, so the container started empty and kept nothing. The project folder was mounted from the Mac. The local build cache was off. The only Gradle state shared between builds was the cache server; the image, the Docker VM and the Mac were the same for every build. Each build is one run, not an average. Each used a new copy of the project's files. Our first set of runs started from a project folder that still held build output from an earlier check on the Mac; we then repeated every run from a clean copy and got the same results.
docker run --rm -e FSCACHE_PASSWORD=<your password> \
-v "$PWD/project:/work/one" -w /work/one \
gradle:9.8.0-jdk21 gradle :core:compileJava --console=plain --no-daemon
The project is the four-module build from Gradle build cache for a monorepo. We looked at the :core:compileJava task.
| Run | What changed | Result |
|---|---|---|
| 1 | First container | Ran |
| 2 | New container, same code, same mount path | FROM-CACHE |
| 3 | New container, same code, mounted at a different path | FROM-CACHE |
| 4 | New container, a line of main code edited | Ran |
| 5 | New container, no password passed | Ran. A 401 line, then the remote cache disabled. BUILD SUCCESSFUL |
Runs 1 to 4 ended with BUILD SUCCESSFUL and printed no 401 line. The server's address was host.docker.internal, which is how a container on Docker Desktop reaches the Mac.
The settings file
This is the cache part of settings.gradle.kts we used. It reads the password from an environment variable, so the password is not in the project. We used plain HTTP because the test ran on one machine; use HTTPS across a network.
buildCache {
local { isEnabled = false }
remote<HttpBuildCache> {
url = uri("http://host.docker.internal:18250/")
isPush = true
isAllowInsecureProtocol = true
credentials { username = "ci"; password = System.getenv("FSCACHE_PASSWORD") }
}
}
What this means for CI
- In our runs no Gradle home or build output had to be kept between builds. The cache lives on the server, so an empty container is enough (runs 2 and 3).
- In our runs the folder path did not matter for this task. The project was mounted at
/work/onein run 2 and/work/twoin run 3, and both restored. - A missing password did not fail the build. Run 5 printed this and carried on without the cache, so look for it in your CI log when builds suddenly get slow (the lines in between are left out):
Could not load entry e3c6ab11d62f857d59bb3a92b7941080 from remote build cache: Loading entry from 'http://host.docker.internal:18250/e3c6ab11d62f857d59bb3a92b7941080' response status 401: Unauthorized
...
The remote build cache was disabled during the build due to errors.
For more on this error, see Gradle remote build cache authentication and 401 errors.
The last test ran two builds inside one container with Gradle's background process on. The first build had no password and printed the 401 line. Then we set the password in the same shell, deleted core/build, and the second build printed :core:compileJava FROM-CACHE. So the background process picked up the later password; no restart was needed. We saw the same in an earlier run on the Mac itself, with no container.
We did not test other CI products, x86 machines, a cache on another host, HTTPS, or Windows. The Linux here is the one inside Docker Desktop on a Mac.
If it does not come from the cache
Work through why the hit rate is low and Jenkins and CI build cache. For read-only developer logins, see CI writes, developers read.
Run on Gradle 9.8.0 (image gradle:9.8.0-jdk21, Linux aarch64, Java 21) with FosterStack Cache 0.2.1, Docker Desktop 4.87 on macOS (arm64).