Infrastructure worth keeping — and kept patched.

Gradle build cache tutorial: see FROM-CACHE with a self-hosted cache

Start a cache server, point a small Gradle project at it, build twice, and watch the second build take its work from the cache. Everything below was run for real: release 0.2.1 of the server and Gradle 9.8.0.

1. Start the server

Download and verify the release as shown in Verify a release yourself, unpack it, and run it with a password and a size cap:

FSCACHE_ADDR=127.0.0.1:8080 FSCACHE_DATA_DIR=./data \
  FSCACHE_USERNAME=gradle FSCACHE_PASSWORD=demo-pass \
  FSCACHE_MAX_BYTES=1073741824 ./fscache

Prefer a container? Gradle build cache with Docker Compose has the same settings. (My test run used port 18090 instead of 8080; the outputs below do not show the port.)

2. A small project

Four files. settings.gradle.kts says where the cache is:

rootProject.name = "demo"

// FosterStack Cache — remote Gradle build cache.
// https://github.com/fosterstack/cache
buildCache {
    local { isEnabled = false }          // so a hit can only come from the remote
    remote<HttpBuildCache> {
        url = uri("http://127.0.0.1:8080/")
        isPush = true
        credentials {
            username = "gradle"
            password = System.getenv("FSCACHE_PASSWORD")
        }
    }
}

Turning the local cache off is only for this test. It means a hit can only have come from the remote. build.gradle.kts is one line, plugins { java }. gradle.properties switches caching on, which the buildCache block does not do by itself:

org.gradle.caching=true

And src/main/java/demo/App.java is any small class.

3. Build once

export FSCACHE_PASSWORD=demo-pass
gradle build --build-cache

> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :jar
> Task :assemble
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build
BUILD SUCCESSFUL in 5s
2 actionable tasks: 2 executed

Nothing came from the cache, and every cacheable task ran. Gradle stored the compile result on the server. The status page agrees:

$ curl -s -u gradle:demo-pass localhost:8080/statusz | grep -E '"store_entries"|"cache_hits"|"cache_misses"'
  "store_entries": 1,
  "cache_hits": 0,
  "cache_misses": 1,

4. Clean, and build again

gradle clean
gradle build --build-cache

> Task :compileJava FROM-CACHE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :jar
> Task :assemble
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build
BUILD SUCCESSFUL in 5s
2 actionable tasks: 1 executed, 1 from cache

There it is: :compileJava FROM-CACHE and "1 from cache". The server counted the hit:

$ curl -s -u gradle:demo-pass localhost:8080/statusz | grep -E '"store_entries"|"cache_hits"|"cache_misses"'
  "store_entries": 1,
  "cache_hits": 1,
  "cache_misses": 1,

The one thing that tripped me up

My first attempt failed before any build ran, because I used http://localhost:...:

FAILURE: Build failed with an exception.

* What went wrong:
Using insecure protocols with remote build cache, without explicit opt-in, is unsupported.

Gradle refuses plain http:// unless you set isAllowInsecureProtocol = true. With Gradle 9.8.0, the address 127.0.0.1 was allowed without it, but the name localhost was refused, with or without a credentials block. That is why the file above uses 127.0.0.1. For a real server, use HTTPS and skip the opt-in. Gradle sends the password with every request.

If your second build shows no FROM-CACHE, work through Gradle remote build cache not working. The status codes are explained in Gradle build cache HTTP status codes.

Gradle and Maven setup FosterStack home