Skip to content
SYS.DOCS // DOCS

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.

  • 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 ReadWriteOnce volume. The cluster default is used unless you pick one.

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.

  1. Turn on Persistent BuildKit under Build cache.
  2. Set the storage size, placement, and daemon resources.
  3. Save the scale set.
FieldDefaultNotes
Storage30GiSize of the cache volume, as a Kubernetes quantity.
Storage classcluster defaultFixed once the volume exists.
Node poolanyPins the daemon to a node pool, for example metal servers with local NVMe disks.
Apply taint tolerationsonLets the daemon schedule onto a tainted node pool.
BuildKit daemon250m / 512Mi requests, 4 / 8Gi limitsCPU 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.

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-from and cache-to inputs 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: true on the build step. With push: false and no load, the image stays in the daemon’s cache only.
  • Plain docker build through the Docker-in-Docker sidecar does not use the daemon. Switch those builds to docker 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.

  • 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.

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 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.