Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions gradle-build-cache-tutorial/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gradle build cache tutorial: see FROM-CACHE with a self-hosted cache</title>
<meta name="description" content="Run a self-hosted Gradle build cache, build a small project twice, and see FROM-CACHE on the second build. Real output from server 0.2.1 and Gradle 9.8.0, including the plain-HTTP error.">
<link rel="canonical" href="https://fosterstack.com/gradle-build-cache-tutorial/">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<style>
:root {
--bg: #ffffff; --fg: #16181d; --muted: #5b6472; --line: #e4e7ec;
--accent: #0d7a5f; --accent-fg: #ffffff; --card: #f6f8f9; --warn-bg: #fff7ed; --warn-line: #fdba74;
--mark: #0f4d66;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #101318; --fg: #e8eaf0; --muted: #98a2b3; --line: #262c36;
--accent: #2fbf95; --accent-fg: #08110e; --card: #171c23; --warn-bg: #241a10; --warn-line: #7c4a12;
--mark: #e8eaf0;
}
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg); color: var(--fg);
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
}
main { max-width: 44rem; margin: 0 auto; padding: 0 1.25rem 4rem; }
header.site { max-width: 44rem; margin: 0 auto; padding: 1.25rem; display: flex; align-items: baseline; gap: .6rem; }
.logo { font-weight: 700; font-size: 1.05rem; letter-spacing: -.01em; }
.logo span { color: var(--accent); }
.logo .mark { width: 1.2em; height: 1.2em; vertical-align: -.24em; margin-right: .4em; }
.tag { color: var(--muted); font-size: .85rem; }
.notice {
background: var(--warn-bg); border: 1px solid var(--warn-line); border-radius: 8px;
padding: .7rem 1rem; font-size: .92rem; margin: 1.5rem 0 2.5rem;
}
h1 { font-size: 2rem; line-height: 1.2; letter-spacing: -.02em; margin-bottom: 1rem; }
.lede { font-size: 1.12rem; color: var(--muted); margin-bottom: 2rem; }
h2 { font-size: 1.25rem; margin: 3rem 0 .9rem; letter-spacing: -.01em; }
p + p { margin-top: .8rem; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: .8rem; margin-top: 1rem; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 1rem 1.1rem; }
.card h3 { font-size: .98rem; margin-bottom: .35rem; }
.card p { font-size: .9rem; color: var(--muted); }
ul.trust { list-style: none; margin-top: .5rem; }
ul.trust li { padding: .45rem 0 .45rem 1.6rem; position: relative; }
ul.trust li::before { content: "✓"; position: absolute; left: .2rem; color: var(--accent); font-weight: 700; }
code, pre { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; }
code { background: var(--card); border: 1px solid var(--line); border-radius: 5px; padding: .1em .35em; }
pre code { background: none; border: 0; padding: 0; color: inherit; font-size: 1em; }
button {
padding: .7rem 1.3rem; font-size: 1rem; font-weight: 600; cursor: pointer;
background: var(--accent); color: var(--accent-fg); border: 0; border-radius: 8px;
}
button:disabled { opacity: .6; cursor: default; }
table { width: 100%; border-collapse: collapse; font-size: .92rem; margin-top: 1rem; }
th, td { text-align: left; padding: .55rem .6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
th { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
pre { background: #0f172a; color: #e2e8f0; padding: .9rem 1rem; border-radius: 8px;
overflow-x: auto; font-size: .9rem; line-height: 1.5; }
.cta-row { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 1.1rem; }
.btn { display: inline-block; padding: .55rem 1rem; border-radius: 8px; font-weight: 600;
text-decoration: none; background: var(--accent); color: #fff; }
.btn-secondary { background: transparent; color: inherit; border: 1px solid #cbd5e1; }
.muted-note { color: var(--muted); font-size: .93rem; margin-top: 1.1rem; }
footer { border-top: 1px solid var(--line); margin-top: 4rem; padding: 1.5rem 1.25rem 3rem; }
footer div { max-width: 44rem; margin: 0 auto; color: var(--muted); font-size: .82rem; }
a { color: var(--accent); }
</style>
</head>
<body>
<header class="site">
<div class="logo"><svg class="mark" viewBox="0 0 96 96" aria-hidden="true" focusable="false"><rect x="12" y="14" width="72" height="18" rx="5" fill="var(--mark)"/><rect x="12" y="39" width="48" height="18" rx="5" fill="var(--mark)"/><rect x="12" y="64" width="26" height="18" rx="5" fill="var(--accent)"/></svg>foster<span>stack</span></div>
<div class="tag">Infrastructure worth keeping — and kept patched.</div>
</header>
<main>
<h1>Gradle build cache tutorial: see FROM-CACHE with a self-hosted cache</h1>
<p class="lede">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.</p>

<h2>1. Start the server</h2>
<p>Download and verify the release as shown in <a href="/verify-a-release/">Verify a release yourself</a>, unpack it, and run it with a password and a size cap:</p>
<pre><code>FSCACHE_ADDR=127.0.0.1:8080 FSCACHE_DATA_DIR=./data \
FSCACHE_USERNAME=gradle FSCACHE_PASSWORD=demo-pass \
FSCACHE_MAX_BYTES=1073741824 ./fscache</code></pre>
<p>Prefer a container? <a href="/gradle-build-cache-docker-compose/">Gradle build cache with Docker Compose</a> has the same settings. (My test run used port 18090 instead of 8080; the outputs below do not show the port.)</p>

<h2>2. A small project</h2>
<p>Four files. <code>settings.gradle.kts</code> says where the cache is:</p>
<pre><code>rootProject.name = &quot;demo&quot;

// 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&lt;HttpBuildCache&gt; {
url = uri(&quot;http://127.0.0.1:8080/&quot;)
isPush = true
credentials {
username = &quot;gradle&quot;
password = System.getenv(&quot;FSCACHE_PASSWORD&quot;)
}
}
}</code></pre>
<p>Turning the local cache off is only for this test. It means a hit can only have come from the remote. <code>build.gradle.kts</code> is one line, <code>plugins { java }</code>. <code>gradle.properties</code> switches caching on, which the <code>buildCache</code> block does not do by itself:</p>
<pre><code>org.gradle.caching=true</code></pre>
<p>And <code>src/main/java/demo/App.java</code> is any small class.</p>

<h2>3. Build once</h2>
<pre><code>export FSCACHE_PASSWORD=demo-pass
gradle build --build-cache

&gt; Task :compileJava
&gt; Task :processResources NO-SOURCE
&gt; Task :classes
&gt; Task :jar
&gt; Task :assemble
&gt; Task :compileTestJava NO-SOURCE
&gt; Task :processTestResources NO-SOURCE
&gt; Task :testClasses UP-TO-DATE
&gt; Task :test NO-SOURCE
&gt; Task :check UP-TO-DATE
&gt; Task :build
BUILD SUCCESSFUL in 5s
2 actionable tasks: 2 executed</code></pre>
<p>Nothing came from the cache, and every cacheable task ran. Gradle stored the compile result on the server. The status page agrees:</p>
<pre><code>$ curl -s -u gradle:demo-pass localhost:8080/statusz | grep -E &#x27;&quot;store_entries&quot;|&quot;cache_hits&quot;|&quot;cache_misses&quot;&#x27;
&quot;store_entries&quot;: 1,
&quot;cache_hits&quot;: 0,
&quot;cache_misses&quot;: 1,</code></pre>

<h2>4. Clean, and build again</h2>
<pre><code>gradle clean
gradle build --build-cache

&gt; Task :compileJava FROM-CACHE
&gt; Task :processResources NO-SOURCE
&gt; Task :classes UP-TO-DATE
&gt; Task :jar
&gt; Task :assemble
&gt; Task :compileTestJava NO-SOURCE
&gt; Task :processTestResources NO-SOURCE
&gt; Task :testClasses UP-TO-DATE
&gt; Task :test NO-SOURCE
&gt; Task :check UP-TO-DATE
&gt; Task :build
BUILD SUCCESSFUL in 5s
2 actionable tasks: 1 executed, 1 from cache</code></pre>
<p>There it is: <code>:compileJava FROM-CACHE</code> and "1 from cache". The server counted the hit:</p>
<pre><code>$ curl -s -u gradle:demo-pass localhost:8080/statusz | grep -E &#x27;&quot;store_entries&quot;|&quot;cache_hits&quot;|&quot;cache_misses&quot;&#x27;
&quot;store_entries&quot;: 1,
&quot;cache_hits&quot;: 1,
&quot;cache_misses&quot;: 1,</code></pre>

<h2>The one thing that tripped me up</h2>
<p>My first attempt failed before any build ran, because I used <code>http://localhost:...</code>:</p>
<pre><code>FAILURE: Build failed with an exception.

* What went wrong:
Using insecure protocols with remote build cache, without explicit opt-in, is unsupported.</code></pre>
<p>Gradle refuses plain <code>http://</code> unless you set <code>isAllowInsecureProtocol = true</code>. With Gradle 9.8.0, the address <code>127.0.0.1</code> was allowed without it, but the name <code>localhost</code> was refused, with or without a <code>credentials</code> block. That is why the file above uses <code>127.0.0.1</code>. For a real server, use HTTPS and skip the opt-in. Gradle sends the password with every request.</p>

<p class="muted-note">If your second build shows no <code>FROM-CACHE</code>, work through <a href="/gradle-remote-cache-not-working/">Gradle remote build cache not working</a>. The status codes are explained in <a href="/gradle-build-cache-http-status-codes/">Gradle build cache HTTP status codes</a>.</p>
<div class="cta-row">
<a class="btn" href="/migrate-build-cache-node/">Gradle and Maven setup</a>
<a class="btn btn-secondary" href="/">FosterStack home</a>
</div>
</main>
<footer>
<div>
<p>FosterStack is not affiliated with, endorsed by, or sponsored by Gradle Inc.
"Gradle" and "Develocity" are trademarks of Gradle Inc., used here only to identify
compatibility. FosterStack contains no Gradle Inc. code; it is a clean-room implementation
of the documented remote build cache HTTP protocol. Apache Maven is a trademark of the
Apache Software Foundation; the Maven Build Cache Extension is Apache's project, not
ours.</p>
<p style="margin-top:.6rem">© 2026 FosterStack · <a href="/regulated/">For regulated markets</a> · <a href="https://github.com/fosterstack/cache">github.com/fosterstack/cache</a> · <a href="mailto:hello@fosterstack.com">hello@fosterstack.com</a></p>
</div>
</footer>
</body>
</html>
4 changes: 4 additions & 0 deletions sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://fosterstack.com/gradle-build-cache-tutorial/</loc>
<lastmod>2026-10-08</lastmod>
</url>
</urlset>
Loading