How do I change the cache logins without breaking CI?
In our runs, changing the logins on your FosterStack Cache server did not fail any build. The old login is refused at once, so a job that still sends it builds without the cache and prints a warning. To keep those jobs using the cache while you update CI, restart the server and list your old read-write login as the read-only one. Those jobs can then read from the cache but not write to it.
What happens when you change a login
- The old login stops working at once. The server reads its logins when it starts. In our runs the old login was refused (401) right after the restart.
- In our runs the build still succeeded. A job with the old login built everything locally, restored nothing, and warned. Gradle also printed that it disabled the remote cache for the rest of that build.
- The stored results stay. After the restart, a job with the new login restored all four cached entries.
The order that keeps the cache working
Our advice, not a test result. We ran steps 2 and 4 and the checks in the table at the end. Steps 1 and 3 are advice, and so is the simple way below.
- Pick a new username as well as a new password for the read-write login. The read-only and read-write usernames must differ. In step 2 your old read-write username becomes the read-only username, so the new one has to be different.
- Restart the server with the new read-write login, and with your old read-write login as the read-only login. Set
FSCACHE_USERNAMEandFSCACHE_PASSWORDto the new pair. SetFSCACHE_RO_USERNAMEandFSCACHE_RO_PASSWORDto the old read-write pair. A job that still has the old login can now restore results. It cannot store new ones, because the server takes one read-write login and one read-only login. - Update the secrets in CI. Jobs that start after that use the new login and can store results again.
- Restart the server once more with the read-only login you want from now on. This ends the old login. A job that still has it runs without the cache.
Developers' old read-only login stops working at step 2. The server takes only one read-only login, and until step 4 it is your old read-write login. Developers have no working login in between. If that matters, use the simple way below.
If you can pause CI for a few minutes, the simple way works too. Restart the server with the new logins and update the CI secrets in the same window. Jobs that run in between lose the cache and still pass.
Changing only the read-only password is the easy case. The read-write login is not touched, and CI keeps storing results.
A restart also means a moment when the server does not answer. See what happens to my build when the cache server is down and does the build cache survive a restart.
Is the order really needed?
Not to keep builds green. Every build in our runs succeeded, in every order. The order only decides how many jobs run without the cache. We counted CI jobs that got a 401 in three orders:
- Server first, CI later (3 jobs run in between): 3 jobs got a 401.
- CI first, server later (2 jobs run in between): 2 jobs got a 401.
- The four steps above (3 CI jobs ran in between, and all restored 4 of 4): 1 job got a 401, a job we deliberately left on the old login until the end. The developers' old read-only login, tried directly, also got a 401.
If it goes wrong
The warning to search for in a Gradle log is response status 401: Unauthorized. It means the job sent a login the server does not accept. Check the secret, then check the username as well as the password. A wrong username with the right password was refused too. See Gradle build cache HTTP status codes and Gradle remote build cache authentication.
The server did not print any password in its log, in /statusz or in /metrics. About logins, /statusz only says whether login is on.
For how to give CI the read-write login and developers the read-only one, see let CI write and developers read and use a remote build cache in GitHub Actions.
How we checked
| Login sent | Server state | What happened |
|---|---|---|
| Read-write, then read-write again | Old logins | First build stored, second restored 4 of 4 |
| Read-only | Old logins | Restored 4 of 4 |
| Old read-write, old read-only | Restarted with new passwords | Both builds succeeded, restored 0 of 4, logged response status 401: Unauthorized |
| New read-write, new read-only | Restarted with new passwords | Both restored 4 of 4 |
| Old read-write | Old read-write login set as read-only | Restored 4 of 4. A direct write got 403 |
| New read-write | Same | Restored 4 of 4, writes allowed |
| Old read-only (developers), a direct request | Same | 401, because the read-only slot now holds another login |
| Wrong username with right password, or right username with wrong password | New logins | 401 on read and write |
| Read-only username with the read-write password, or the reverse | New logins | 401 on read and write |
| No login | New logins | 401. The health check at /healthz answered 200 |
| Old read-only password after changing only the read-only pair | Read-write pair unchanged | 401. The read-write build still restored 4 of 4 |
The server also refused to start with identical read-only and read-write usernames, and with a read-only login but no read-write login. Both exit with an error that says why.
What: FosterStack Cache 0.2.1 and Gradle 9.8.0 with Java 21 on a Mac (arm64), one machine. Each build used a fresh copy of a four-module project, a new Gradle home, no daemon and the compile tasks only. The cache address and login reached the build only as environment variables. All logins were made up for the test. 23 builds ran, and every one succeeded.
Limits: the table shows Gradle. We did not run Maven for this page. The builds in the bridge steps found everything in the cache, so no job tried to store a result with a read-only login. Earlier, in our server-down runs, a refused store (403) was a warning and the build still succeeded. We changed logins only by restarting the server. The v0.2.1 source has no reload signal, and we did not test Maven, a restart while a build was running, or your own CI runners (the second run, below, used a GitHub-hosted machine). The counts of 401 jobs come from one scripted run of each order, not a statistic.
The first run above was on a Mac with 0.2.1. We ran this page again on a GitHub-hosted Linux machine with FosterStack Cache 0.2.2, Gradle 9.8.0 and Java 21, on a four-module project with the same steps. All eleven rows of the table gave the same results. The three orders gave the same counts of jobs that got a 401: 3, 2 and 1. Every build succeeded. The two refusals to start (identical usernames, and a read-only login without a read-write one) exited with an error that says why. We searched the server's logs, /statusz and /metrics for each made-up test password. None appeared. The run and its log are public.