Upgrade, back up and move a build cache server
Can you upgrade a build cache, back it up or move it to a new server without losing what it holds? We ran all three with FosterStack Cache, and this page shows what happened.
The short answer
- In our test an upgrade lost nothing, and a rollback to the old release could still read what the new one had written.
- You usually do not need a backup. If the cache is lost, builds are slower until it refills, and nothing breaks.
- To move a Maven cache, change the address in
.mvn/maven-build-cache-config.xml. Nothing else in the project changes. - If you only change the address, the first build is a normal full build and fills the new server. Nothing fails.
- If you want the new server to be ready at once, stop the old one, copy its data folder, and start the new server on the copy.
Our advice, not a test result
- Use the same volume and the same settings (environment variables for the password and size cap) as the old container.
- Pin the exact tag you want rather than
latest. - Stop the server before you copy its data. We did not test copying the files while the server is running, so we cannot say that is safe.
The upgrade
docker pull ghcr.io/fosterstack/cache:0.2.2
docker stop fscache && docker rm fscache
docker run -d --name fscache -p 127.0.0.1:8080:8080 \
-v fscache-data:/home/nonroot \
ghcr.io/fosterstack/cache:0.2.2
What we saw
First, from 0.1.0 to 0.2.1 and back:
| Step | Result |
|---|---|
| 0.1.0: store three entries on a fresh volume | 201 each; its metrics show 3 entries, 29 bytes |
| Stop 0.1.0, start 0.2.1 on the same volume | starts normally; the status page shows "version": "v0.2.1", 3 entries, 29 bytes; all three values read back |
| 0.2.1: store two more | 201 each; 5 entries |
| Stop 0.2.1, start 0.1.0 on the same volume again | starts normally; all values read back, including the two written by 0.2.1 |
We then did the same from 0.2.1 to 0.2.2, the latest release, and back:
| Step | Result |
|---|---|
| 0.2.1: store three entries on a fresh volume | 201 each; the status page shows 3 entries, 29 bytes |
| Stop 0.2.1, start 0.2.2 on the same volume | starts normally; the status page shows "version": "v0.2.2", 3 entries, 29 bytes; all three values read back |
| 0.2.2: store two more | 201 each; 5 entries, 49 bytes |
| Stop 0.2.2, start 0.2.1 on the same volume again | starts normally; the status page shows 5 entries, 49 bytes; all five values read back |
Two differences between the releases
- 0.1.0 has no status page.
/statuszreturned 404 there. 0.2.1 has it. The metrics page is on both. - The entry-count metric reads 0 after a restart, on both releases. After the rollback, 0.1.0's
fscache_store_entriesread 0 although every value was readable. The upgrade did not cause that: a volume only 0.1.0 ever wrote showed 3 entries before a plain restart and 0 after it, with the data still readable. 0.2.1's metrics read 0 after a restart until the next upload too, and so did 0.2.2's: right after we started it on the 0.2.1 volume,fscache_store_entriesread 0 while/statuszshowed 3 entries (see is my build cache server healthy). On 0.2.1,/statuszstill showed the real count, 3 entries and 9 bytes, so read it there.
Back up and restore a self-hosted build cache
A big cache takes days of builds to fill again, and you may want to move it to a new host. A backup is worth it only if refilling is expensive for you, or if you are moving the cache to a new server. To simply empty a cache, see how to clear or reset a build cache.
Where the data lives
The Docker command in our guides mounts a volume at /home/nonroot. The server keeps its blobs and index in a data folder inside it, and it runs as user 65532. Backing up the volume backs up the cache.
1. Stop the server, then back up the volume
We stopped the container first. The container image has no shell, so a second small container does the copying:
docker stop fscache
docker run --rm -v fscache-data:/data:ro -v "$PWD":/backup ubuntu:24.04 \
tar czf /backup/fscache-data.tgz -C /data .
In our test, with three small entries, the archive held a data/blobs/ folder with one folder per entry prefix, and was 578 bytes.
2. Restore it into a new volume
To check it, we deleted the container and the volume, then restored into a new, empty one. We ran this on the 0.2.1 image; use the release that wrote the backup:
docker volume create fscache-data-restored
docker run --rm -v fscache-data-restored:/data -v "$PWD":/backup:ro ubuntu:24.04 \
sh -c 'tar xzf /backup/fscache-data.tgz -C /data'
ls -ln /data # inside the helper: the files are owned by 65532
Listing the restored volume showed the data folder still owned by user and group 65532, which is what the server runs as. If you restore as a different user, the server will not be able to write.
3. Start the server on the restored volume
docker run -d --name fscache -p 127.0.0.1:8080:8080 \
-v fscache-data-restored:/home/nonroot ghcr.io/fosterstack/cache:0.2.1
The status page and the entries were all back:
$ curl -s localhost:8080/statusz | grep -E '"store_entries"|"store_bytes"'
"store_bytes": 32,
"store_entries": 3,
$ curl -s localhost:8080/alpha
value-alpha
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/nothere
404
Move a Maven build cache to a new server
You are replacing the machine that runs your build cache and your builds use the Apache Maven Build Cache Extension. Here is what to change, what to expect from the first build, and how to keep the cache warm.
What we saw
| Build | Server it used | Result |
|---|---|---|
| 1 | Old server, empty | Built the project and saved the result to the server |
| 2 | Old server, from another machine | Restored from the cache, no compile |
| 3 | New server, empty, only the address changed | Full build, saved to the new server |
| 4 | New server, from another machine | Restored from the cache, no compile |
| 5 | New server started on a copy of the old data folder | Restored from the cache, no compile |
Option 1: change the address only
Start the new server, then edit the url in the cache config and commit it. We changed this one line and nothing else:
<remote enabled="true" saveToRemote="true" id="fosterstack-cache">
<url>http://127.0.0.1:18151/</url>
</remote>
Build 3 had no cached result to find, so Maven compiled the project and saved it:
[INFO] Attempting to restore project demo:demo from build cache
[INFO] Compiling 1 source file with javac [debug release 17] to target/classes
[INFO] Saved to remote cache http://127.0.0.1:18151//v1.1/demo/demo/ebb9bceba2f7a6bc/demo.jar
From then on, every other machine pointing at the new address gets the result from the new server, as build 4 did. Anyone who still has the old address in their config keeps using the old server until they update.
Option 2: copy the data folder first
This keeps the new server warm from the first build. Stop the old server, copy its data folder, and start the new server with its data folder set to the copy. See back up and restore for why the old server should be stopped first.
cp -R data-old data-new-copied
FSCACHE_ADDR=127.0.0.1:18152 FSCACHE_DATA_DIR=$PWD/data-new-copied fscache
Build 5 found the result straight away, with no compile:
[INFO] Attempting to restore project demo:demo from build cache
[INFO] Found cached build, restoring demo:demo from cache by checksum ebb9bceba2f7a6bc
The new server's status page agreed: 2 hits and 0 misses at that point.
How to tell it worked
Look for Found cached build, restoring in the Maven output. You may also see Saved to remote cache lines on a build that restored. In our run these were a small report file that the extension saves each time, not a rebuild. If a build prints Compiling instead of Found cached build, it did not find a stored result.
Credentials and other settings
If the new server has logins, Maven reads them from the <server> entry in settings.xml whose id matches the id on the <remote> line. See Maven remote build cache setup for that, and behind a corporate proxy if your builds go through one. To move Gradle builds too, see one server for Gradle and Maven.
How we checked
- Upgrade: we ran two pairs of releases, 0.1.0 with 0.2.1, and 0.2.1 with 0.2.2. In each pair we stored entries on the older release, started the newer one on the same data volume, wrote more, and then went back to the older one. Docker, one named volume at
/home/nonroot, no password, stopped and started cleanly between versions. - Backup and restore: run for real on the 0.2.1 image with plain Docker, three small entries.
- Maven move: a small Maven project, Maven 3.10.0, extension 1.2.3, macOS (arm64). Every build used a fresh copy of the project and an empty local build cache, so the only thing shared was the cache server. All five builds ended with
BUILD SUCCESS. Every step was run for real with FosterStack Cache 0.2.1. - Again on Linux: we ran both release pairs, the backup and restore commands and the five Maven builds on a GitHub-hosted Linux machine. The release images were checked against their published digests and verified with cosign first. Everything matched this page:
- the status page: 3 entries and 29 bytes, then 5 entries and 49 bytes, and the version strings
- a 404 for
/statuszon 0.1.0, and the entry-count metric reading 0 after a restart - the restored data folder owned by 65532, and the restored server: 32 bytes, 3 entries,
value-alpha, and a 404 for a missing key - Maven: 2 hits and 0 misses on the copied data folder
- Not tested: upgrading or backing up while the server is running, any other pair of releases (there is no 0.2.0 release; see the changelog), restoring into a different version of the server than the one that wrote the data, Kubernetes or Compose rollouts (the same idea applies to the volume behind the claim, but we only ran plain Docker), the FIPS image, or a cache that was close to its size cap.
Related: how releases are built and signed and verify a release yourself, so you can check the new version before you run it.