Adding a local cache to a Forgejo runner
Using a temporary local BuildKit cache in a Forgejo runner to speed up repeated registry control plane builds on limited hardware.
- Reusing a local BuildKit cache between Forgejo runner jobs.
- Why caching matters on smaller hosts with limited CPU, disk, and network headroom.
- Keeping the cache disposable instead of making the runner a storage layer.
- Wiring Buildx cache paths under `/tmp` through bake and workflow commands.
A temporary local BuildKit cache on the Forgejo runner made repeated registry control plane test builds much faster.
That matters most on smaller hosts where CPU, disk, and network headroom are already tight. Re-downloading packages, build inputs, and intermediate artifacts on every run wastes time and puts unnecessary load on the machine.
The goal was to keep enough build state around to speed up repeated testing
without turning the runner into a storage service. In this setup the workflow
points Buildx at /tmp, reuses that cache across runs, and prunes old data so
it does not grow without bound.
On a constrained box, that tends to make CI feel more predictable. Jobs spend less time waiting on the network, dependency-heavy tests are less punishing, and the host does not have to work as hard to deliver the same result.
In my case, the cache is intentionally temporary. The Forgejo workflow points
Buildx at a local path under /tmp, reuses it across builds, and then prunes
old cache data so the runner does not grow without bound.
This runner setup uses Docker-in-Docker, so that cache belongs to the build
environment the runner is talking to rather than to a separate long-lived cache
service.
The relevant part in docker-bake.hcl looks like this:
target "common" {
cache-from = [
"type=local,src=/tmp/buildkit-registry-control-plane",
]
cache-to = [
"type=local,dest=/tmp/buildkit-registry-control-plane,mode=max",
]
}And the workflow uses that cache during validation and publish runs:
- name: Validate multi-arch build
run: |
docker buildx bake \
--allow=fs=/tmp \
validate-multiarch
- name: Publish multi-arch images
run: |
docker buildx bake \
--allow=fs=/tmp \
publishThe main permission detail is that Buildx needs access to that /tmp path.
That is why the bake command explicitly allows filesystem access there with
--allow=fs=/tmp. After the build, the workflow makes sure the directory
exists and then prunes older cache content:
- name: Prune expired BuildKit cache
if: always()
run: |
mkdir -p /tmp/buildkit-registry-control-plane
docker buildx prune \
--builder forgejo-builder \
--filter until=240h \
--force || trueThat matches what I wanted from the runner on a limited host: keep repeated testing fast, keep the cache local, and keep it disposable enough that it does not quietly turn into another storage management problem.