A self-hosted, drop-in remote build cache for Gradle — and for Maven through the Apache Maven Build Cache Extension (acceptance-tested on every push and release). Ships as a single static binary or a distroless container image, MIT-licensed, with security patches under a standing policy (SECURITY.md). Every image is keylessly signed (Sigstore); every binary archive is covered by a signed checksums file; both carry SLSA provenance naming the CI run that built them. "Verifying a release" has the commands.
Website: fosterstack.com · Docs: Install · Docker · Kubernetes · Gradle · Maven
Gradle Inc. is discontinuing the free, standalone Develocity Build Cache
Node — no further distribution, support, or updates after
December 31, 2026. Teams running the free cache node on open-source
Gradle (no Develocity subscription) need a maintained, drop-in replacement.
FosterStack Cache speaks Gradle's documented HttpBuildCache
protocol (a plain content-addressed GET/PUT over HTTP) and the Apache
Maven Build Cache Extension's remote HTTP mode
(GET/PUT/HEAD) — same server, same core. The Gradle path is
acceptance-tested against a real multi-module build in CI, and so is the Maven
path: a real multi-module Maven project with the extension (versions 1.2.0 and 1.2.3 )
restores its second build from the remote cache with byte-identical jars,
and wrong or missing credentials fail the documented way. The Maven suite is a
release-blocking check.
Shipped:
- Cache server core: content-addressed filesystem blob store,
bboltmetadata index, size-capped LRU eviction — all tested. - HTTP surface:
GET/PUT/HEAD, optional Basic Auth, Prometheus metrics at/metrics, liveness at/healthz. CGO_ENABLED=0static binary — builds and runs today (see below).- CI on every push/PR: tests (race-enabled),
go vet, golangci-lint (staticcheck + a repo-widecrypto/md5/crypto/sha1import ban), gosec, govulncheck, CodeQL, dependency review on PRs, OpenSSF Scorecard, and a public-repo file allowlist. - Release pipeline: signed, provenance-attested container images
(production,
-debug,-fips) on GHCR, plus bare binaries and a signed checksums file. Every release is built only by CI, once. The images are assembled once, and the scans, the acceptance tests and an independent reproducibility rebuild all run on that exact digest; the published images are that same digest, promoted without a rebuild. The daily rescan then re-checks the current release.RELEASING.mddescribes the pipeline stage by stage. - Acceptance-tested against a real multi-module Gradle project on every
push/PR — a from-scratch second build must produce real
FROM-CACHEhits, with the sample's local build cache disabled so a hit is remote evidence. There is no benchmark suite yet; this is a correctness gate, not a performance number. - Deployment docs: Install (binaries + systemd), Docker (with sizing), Kubernetes, Gradle, Maven, and disconnected networks.
Not yet shipped:
- Production use beyond our own CI. Nobody is running this in a real build pipeline yet except us.
- A Helm chart. Deploying to Kubernetes today means applying the plain manifests in docs/kubernetes.md.
- The paid tiers. Single sign-on, high-availability replication, and the license key that unlocks them are not built. Everything in this repository is the free MIT core.
- A CVE patch commitment you can hold us to. We aim to ship fixes for dependency CVEs within 48 hours of public disclosure. That is a stated intention, not a contractual promise, and it will not be one until the paid tiers exist. What already runs today is the detection half: the current release's images are rescanned daily, so a CVE disclosed against bytes we already shipped raises an issue without anyone remembering to look.
Tracked in this repo's issues, which is also where the roadmap gets argued with. There is no published schedule: the list above is direction, not dates.
There is no waitlist and no signup. Pull the image and run it — the quickstart below is the whole gate.
Each item above names the workflow or command that demonstrates it —
Actions history for the CI claims, the commands in
RELEASING.md for signatures and provenance. What those
commands prove, and what they do not, is stated in RELEASING.md itself.
Full core (eviction, size limits, metrics) is free forever under MIT — see
LICENSE. Paid tiers add SSO, HA/replication, a documented CVE
SLA, and compliance support on top of the same open core; nothing is
withheld from the free tier for security.
The security evidence itself is public and free: SBOMs, SLSA provenance, signatures, VEX statements, and the FIPS 140-3 module certificate number are published with every release and verifiable by anyone, with no account and no purchase. Scan verdicts are visible in the public CI logs; they are not yet attached to releases as durable, digest-bound evidence. The FIPS-mode image is publicly pullable too. What the Compliance tier sells is the authored analysis, the vendor signature, and the hours — never access to the bytes or the evidence.
docker run -d -p 8080:8080 ghcr.io/fosterstack/cache:latest
curl localhost:8080/healthz # -> okPin a specific version for anything beyond a first look — the badge above
shows the current one, and docs/docker-deploy.md
covers pinning by digest. The same images, same digests, are mirrored at
docker.io/fosterstack/cache for tooling that defaults to Docker Hub;
GHCR is canonical, and if Docker Hub's anonymous pull limits bite (they
key on the puller, not on us), authenticate or pull from GHCR.
That's the whole install. No registration, no license key for the Community tier — pull, run, point your build tool at it (below).
Every release builds this whole matrix from the same source in the same CI run — there is no primary platform.
| Artifact | Platforms |
|---|---|
Binaries (fscache) |
linux/amd64, linux/arm64, darwin/amd64, darwin/arm64 |
FIPS binaries (fscache-fips) |
linux/amd64, linux/arm64 |
Container images (prod, -debug, -fips) |
multi-arch: linux/amd64 + linux/arm64 |
| Tag | Shell | Use |
|---|---|---|
X.Y.Z |
none | Production |
X.Y.Z-debug |
busybox at /busybox/sh |
Interactive troubleshooting |
X.Y.Z-fips |
none | Production, FIPS 140-3 validated crypto module |
Production and -fips are distroless: the fscache binary and nothing
else, so there is no shell to exec into and no package manager to patch.
:debug adds busybox for troubleshooting, and its shell is at
/busybox/sh — distroless has no /bin/sh, unlike Debian, Alpine or
Wolfi. See
the variant docs
and troubleshooting.
FIPS is Linux-only on purpose: the compliance buyer it serves deploys on
Linux, so a macOS FIPS build would double CI time for a configuration nobody
assesses. Container images ship as multi-arch manifests, so docker pull
resolves the right image by itself on Apple Silicon and Graviton. Windows is
not built and not planned — this is a server that lives next to your CI
runners; develop on Windows via WSL2, which is linux/amd64.
Full install instructions, including which archive to download and a systemd unit: docs/install.md.
The production and -fips images contain only the fscache binary, with no
shell, so you debug them from the outside by attaching a second container that
carries the tools. Three ways below. The two Docker ones were run against this
image on Oct 9 2026; kubectl debug was checked against the Kubernetes
documentation only (details at the end of this section).
Kubernetes: kubectl debug. An ephemeral debug container joins the running
pod (stable since Kubernetes v1.25):
kubectl debug -it <pod> --image=busybox:1.37 --target=<container> -- sh
# inside it:
wget -qO- http://127.0.0.1:8080/healthz
ps--target points the debug container at the fscache container so it can see
its processes (the container runtime has to support that). The pod's network is
shared, so 127.0.0.1:8080 is the cache. Use any image that already has the
tools you need. The debug container inherits the pod's security context: with
the Deployment in docs/kubernetes.md (runAsNonRoot, user
65532) it runs as that non-root user, so it cannot install packages (an image
such as wolfi-base with apk add needs a pod that allows root). An ephemeral container
cannot be changed or removed once added; it goes away with the pod. To debug a
modified copy instead, use kubectl debug <pod> -it --copy-to=<new-name> --image=..., and for the node itself kubectl debug node/<node> -it --image=....
Docker: docker debug.
docker debug <container>This opens a shell in a toolbox attached to the container, even though the image has no shell. It ships with Docker Desktop and was a paid feature (Pro, Team or Business) until Docker Desktop 4.50 (announced 12 Nov 2025), when Docker made it free for all users; Docker's earlier announcement (Docker Desktop 4.33, July 2024) described it as available with a Pro, Team or Business license, so an older Docker Desktop may still need one. See Docker's announcement and the command reference.
Docker without docker debug works on any Docker, with no plan or sign-in:
start a tools container that shares the cache container's network and process
namespaces. (This is the no-shell route; the :debug image variant and its
/busybox/sh are described in the
troubleshooting section.)
docker run --rm -it --network=container:<name> --pid=container:<name> \
cgr.dev/chainguard/wolfi-base sh
# inside it: apk add curl procps, then curl http://127.0.0.1:8080/healthzWhat was tested: docker debug (Docker Desktop's debug plugin 0.0.47 ) gave a
root shell on a running fscache container; the plain-Docker command above
reached /healthz (HTTP 200) on 127.0.0.1:8080 and listed fscache as PID 1;
wolfi-base runs as root and apk add curl works in it; busybox:1.37 run as user 65532 (as the pod's security context would) read /healthz with wget and listed fscache. The tool images in the
examples are not pinned; pin them by digest in anything you automate.
kubectl debug was checked against the Kubernetes documentation but not run
against a cluster here.
git clone https://github.com/fosterstack/cache.git
cd cache
CGO_ENABLED=0 go build -o fscache ./cmd/fscache
./fscacheConfiguration is via environment variables (flags are not yet wired):
| Variable | Default | Meaning |
|---|---|---|
FSCACHE_ADDR |
:8080 |
Listen address |
FSCACHE_DATA_DIR |
./data |
Where blobs and the metadata index live |
FSCACHE_MAX_BYTES |
0 (unbounded) |
Size cap; oldest-unused entries evicted first |
FSCACHE_USERNAME / FSCACHE_PASSWORD |
unset (auth disabled) | HTTP Basic Auth, required together |
FSCACHE_RO_USERNAME / FSCACHE_RO_PASSWORD |
unset | Optional read-only pair: GET/HEAD only, writes get 403. Requires the read-write pair; usernames must differ |
FSCACHE_MAX_BODY_BYTES |
1073741824 (1 GiB) |
Max accepted blob size per PUT |
FSCACHE_MAX_CONCURRENT_UPLOADS |
32 (0 disables the bound) |
Max PUTs in flight; the request past the limit gets 429 with Retry-After and stores nothing |
FSCACHE_TLS_CERT / FSCACHE_TLS_KEY |
unset (plain HTTP) | PEM certificate file (it may hold the chain) and its private key file, required together. When both are set the server serves HTTPS only, on FSCACHE_ADDR, with TLS 1.2 as the minimum version; HTTP/2 is not offered. Each file may be at most 1 MiB. The files are read only at startup: replace them, then restart |
A variable that starts with FSCACHE_ and is not in this table is ignored with one WARN log line naming it (never its value), and, when it is a near miss for a real name, a did_you_mean hint. The server still starts.
In settings.gradle / settings.gradle.kts, point the remote cache at
your server (the whole request path is the cache key, so a trailing slash
on the URL is all that's needed):
buildCache {
remote<HttpBuildCache> {
url = uri("https://cache.example.com/")
isPush = true
// credentials { username = "..."; password = "..." } // if auth is enabled
}
}Basic Auth credentials come from settings.xml <server> conventions, the
Apache extension's standard pattern. See Maven setup in 10 minutes
for the full walkthrough.
Every externally observable behavior of this server is a written requirement with measurable acceptance criteria, and the matrix at docs/quality/traceability.md maps each criterion to its evidence — including, honestly, the criteria that have no sufficient evidence yet. The baseline was extracted from the code and docs and owner-approved; since Sep 8, 2026, acceptance criteria are written before implementation. CI fails if the matrix drifts from its sources.
- Gradle setup · Maven setup
- Migrate off Build Cache Node in 30 minutes
- Install (binaries, systemd, platforms)
- One-command Docker deploy · Deploying on Kubernetes
- Installing on disconnected networks
- Verify our images · Scanning FosterStack in your compliance pipeline
- Releasing · Security policy · Contributing
- Static binary, zero CGO, no OS package surface to patch.
- Approved-only cryptography is enforced by the validated module itself, not
by a list: CI runs the full test suite under
GODEBUG=fips140=only, where Go's FIPS 140-3 module refuses any non-approved algorithm at runtime. Adepguardallowlist covers the static side, permitting only in-boundary crypto imports and denying third-party crypto outright. Both are described inSECURITY.md, including what each does and does not prove. - No telemetry and no required FosterStack connection. The server
reports nothing to us — no usage reporting, no licence check, no update
ping — and today's free core initiates no outbound connections at all, so
it runs identically on a host with no route to the internet. When paid
features ship, traffic you configure will exist (an identity provider for
SSO, peer replicas for replication) — connections to endpoints you
choose, never to FosterStack. Check the current behavior yourself:
lsof -nP -a -p <pid> -iagainst a running server shows one listening socket and accepted inbound connections, nothing else. - See
SECURITY.mdfor the vulnerability disclosure process and patch SLA once published.
MIT — see LICENSE.