Gradle build cache: let CI write, developers only read
Anything that can write to your build cache can change what later builds use. So give the right to write to as few machines as possible. This page sets up a cache where CI uploads and developer laptops only download, and shows the server enforcing it. Everything here was run for real on a 0.2.1 server with Gradle 9.8.0.
1. Give the server two logins
The server takes one read-write login and, optionally, a second read-only login. A client using the read-only one can read cache entries, and any write is refused with a 403.
FSCACHE_USERNAME=ci FSCACHE_PASSWORD=<a long secret> \
FSCACHE_RO_USERNAME=dev FSCACHE_RO_PASSWORD=<another secret> \
./fscache
The two usernames must differ, and the read-only pair only works alongside the read-write pair. Both are in the configuration table.
2. One Gradle setting for everyone
Developers and CI use the same settings.gradle.kts. Only the environment differs:
rootProject.name = "demo"
// FosterStack Cache — remote Gradle build cache.
// https://github.com/fosterstack/cache
buildCache {
remote<HttpBuildCache> {
url = uri("https://cache.example.com/")
// CI pushes; everyone else only reads
isPush = System.getenv("CI") != null
credentials {
username = System.getenv("FSCACHE_USERNAME")
password = System.getenv("FSCACHE_PASSWORD")
}
}
}
On CI, set CI and the read-write login. On a laptop, leave CI unset and use the read-only login. Setting isPush to false on a laptop is the client choosing not to upload. The read-only login is what makes the server enforce it.
3. What happened when I ran it
The server counts what it stores. Each line below is a build of the same small project. (In my test I turned Gradle's local cache off, with local { isEnabled = false } in the same block, so that a hit could only have come from the server. You do not need that line.)
| Who builds | Result | Server after |
|---|---|---|
A developer first, read-only login, CI unset | :compileJava runs. Nothing to read yet, and nothing uploaded. | 0 entries |
CI, read-write login, CI=true | :compileJava runs, and the result is uploaded. | 1 entry |
| The developer again, read-only login | > Task :compileJava FROM-CACHE, "1 from cache" | 1 entry, 1 hit |
The laptop never wrote to the cache, and still got the benefit of CI's work.
If a laptop is set up to push anyway
I then made a code change, so there was something new to upload, and built with CI=true but only the read-only login. The build still succeeded. With -i Gradle logged:
Could not store entry d4f2de693b561fd2c5d60b17a9232d6f in remote build cache: Storing entry at 'http://127.0.0.1:18098/d4f2de693b561fd2c5d60b17a9232d6f' response status 403: Forbidden
The remote build cache was disabled during the build due to errors.
The server stored nothing: it stayed at 1 entry. The same change built by CI with the read-write login was stored (2 entries). So a misconfigured laptop cannot add to the cache, and its build is not broken by that. Note that Gradle switches the remote cache off for the rest of that build once a store fails, so a laptop in this state also stops reading from it for that build.
What this does not give you
There is no per-person identity yet. Everyone who holds the read-write login is the same writer to the server, so keep it to CI. See who can write to a remote build cache and the HTTP status codes.