Persistent BuildKit for Docker Builds
Runner pods are ephemeral, so every Docker build on a private runner starts
from nothing: base images are pulled again and every layer is rebuilt. The
build cache gives a runner scale set a persistent
BuildKit daemon with its own volume. Every
runner of the scale set builds against that daemon over the cluster network, so
unchanged layers and RUN --mount=type=cache mounts are already there on the
next job.
Workflows need no changes. docker buildx build, docker/build-push-action,
and docker/bake-action use the daemon as soon as the job runs on a runner from
the scale set.
Requirements
Section titled “Requirements”- A runner scale set in Docker-in-Docker container mode. See GitHub Actions runners. The build cache is not available in Kubernetes jobs mode.
- A storage class that can provision a
ReadWriteOncevolume. The cluster default is used unless you pick one.
Enable the build cache
Section titled “Enable the build cache”For a new scale set, open Advanced in the Add scale set dialog. For an existing one, open the scale set page and select Settings.
- Turn on Persistent BuildKit under Build cache.
- Set the storage size, placement, and daemon resources.
- Save the scale set.
| Field | Default | Notes |
|---|---|---|
| Storage | 30Gi | Size of the cache volume, as a Kubernetes quantity. |
| Storage class | cluster default | Fixed once the volume exists. |
| Node pool | any | Pins the daemon to a node pool, for example metal servers with local NVMe disks. |
| Apply taint tolerations | on | Lets the daemon schedule onto a tainted node pool. |
| BuildKit daemon | 250m / 512Mi requests, 4 / 8Gi limits | CPU and memory for the daemon. It serves every runner of the scale set at once, so size it for the builds that run in parallel. |
Saving starts the daemon in the runner namespace and re-applies the scale set so runner pods use it from their next job. The scale set Overview shows the daemon state and the volume size.
How builds use it
Section titled “How builds use it”Each runner pod registers a buildx builder named edka that points at the
daemon and selects it through BUILDX_BUILDER. That selection takes precedence
over the builder docker/setup-buildx-action creates, so existing workflows
keep working and just get faster.
jobs: image: runs-on: default steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: context: . push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}cache-fromandcache-toinputs are no longer needed. They keep working if you leave them in.- Pushes happen from the daemon with the credentials the job logged in with. Pushing to the in-cluster registry works without extra steps.
- A later step that runs the image needs
load: trueon the build step. Withpush: falseand noload, the image stays in the daemon’s cache only. - Plain
docker buildthrough the Docker-in-Docker sidecar does not use the daemon. Switch those builds todocker buildx build. - Builds run on the architecture of the node the daemon runs on. For arm64 images, place the daemon on an arm64 node pool.
Cache mounts are shared between concurrent builds the same way they are on a single machine. Layers are content addressed, so two runners building the same base layers share them.
Sizing and placement
Section titled “Sizing and placement”- Storage can be raised later if the storage class supports volume expansion. Kubernetes does not shrink volumes, so a smaller size is rejected.
- Storage class is fixed once the volume exists.
- Node pool is fixed once the volume is bound. Local and topology-aware volumes can only be mounted from the nodes they were created on.
- The daemon keeps its own garbage collection: it holds the volume below 85% used and drops the least recently used layers first, so builds keep working when the volume fills. Raise Storage to keep more.
To change the storage class or placement, delete the scale set and create it again with the new settings.
Access
Section titled “Access”The daemon accepts connections only from the runner pods of its scale set. Other pods in the cluster, including runners of other scale sets, cannot reach it.
Turn the build cache off
Section titled “Turn the build cache off”Turn off Persistent BuildKit and save the scale set. Runner pods go back to building with the Docker-in-Docker sidecar from their next job. Switching the scale set to Kubernetes jobs mode also turns the build cache off. The volume and its layers are kept, so turning the build cache on again resumes warm.
Deleting the scale set removes the daemon and its volume.