Registry Cache
A registry cache stores Docker build layers as a separate image in a container registry, so any builder able to pull from that registry reuses them.
A registry cache is a Docker layer cache stored as an image in a container registry, kept under its own tag and separate from the application image the build produces. Any builder that can pull from that registry can read it, so a build running on a machine that has never seen the project reuses layers produced by an earlier run on a different machine.
That property is what makes the registry backend common in GitHub Actions. A job usually starts on a machine with an empty local build cache, so the cache has to live somewhere the next machine can reach.
Definition
BuildKit, the engine behind current Docker builds, can export its layer cache to an external backend instead of leaving it on local disk. type=registry is one of those backends, documented in the registry cache backend reference, checked on 2026-08-13. The backend writes the cache as a normal registry artifact, so registry authentication, storage quotas, replication, and retention rules apply to it exactly as they apply to an application image.
The exported artifact carries two kinds of content. The first is the layer blobs themselves. The second is a cache config blob, media type application/vnd.buildkit.cacheconfig.v0, which records the dependency graph of the build: which step produced which layer, and the digests of the inputs that step consumed. The config blob is what turns a pile of blobs into a usable cache, because it lets the next build decide whether a step it is about to run matches a step already recorded.
Import is lazy by design. --cache-from type=registry,ref=... fetches the manifest and the config blob first, compares step digests against the graph it is building, and downloads a layer blob only for the steps that match. A build with no matching steps pays for two small metadata requests rather than a full cache download.
One prerequisite trips people up. Docker documents the registry backend as unsupported by the default docker driver, so the build has to run on a builder created with the docker-container, kubernetes, or remote driver, per the builder drivers reference, checked on 2026-08-13.
The backend accepts these parameters, from the same registry backend reference:
| Parameter | Values | Default | What it controls |
|---|---|---|---|
ref | image reference | required | The repository and tag the cache is written to and read from |
mode | min, max | min | Whether every stage is exported or only the final one |
oci-mediatypes | true, false | true | Whether the cache manifest uses OCI media types |
image-manifest | true, false | false | Exports the cache as an OCI image manifest rather than a manifest list |
compression | gzip, estargz, zstd | gzip | Blob compression applied to exported layers |
compression-level | integer | backend specific | Compression effort for the chosen algorithm |
force-compression | true, false | false | Recompresses layers that already exist in another format |
ignore-error | true, false | false | Whether a failed cache export also fails the build |
image-manifest=true matters for registries that reject a manifest list under a tag. It has to be paired with oci-mediatypes=true, and it is the usual fix when an export succeeds against one registry and fails against another with the same workflow file.
A registry cache is distinct from the metadata carried inside a pushed image, which the inline cache term covers. The registry backend writes a dedicated artifact and can therefore record intermediate stages; inline metadata rides along with the image and is limited to what that image already contains.
Example
This workflow builds an image, pushes it to the GitHub Container Registry, and keeps its layer cache under a second tag in the same repository. docker/setup-buildx-action creates a docker-container builder, which satisfies the driver requirement above.
name: image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
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: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=maxThe first run finds nothing at the :buildcache reference, executes every step, then pushes two artifacts. The second run lands on a fresh machine with an empty local cache, resolves the same reference, and imports the layers whose step digests still match. Those steps report CACHED in the build log:
=> CACHED [build 3/5] RUN go mod download
=> CACHED [build 4/5] COPY . .
=> [build 5/5] RUN go build -o /out/app ./cmd/appAfter a successful run the registry repository holds two tags with different jobs:
| Tag | Contents | Top artifact | Pulled by |
|---|---|---|---|
:<sha> | Application image layers and image config | Image manifest or index | A container runtime, at deploy time |
:buildcache | Exported layer blobs and the BuildKit cache config blob | Manifest list by default, image manifest under image-manifest=true | The next build, through cache-from |
The :buildcache tag is a build input rather than a runnable image, so docker run against it fails. Treat it as a storage line item: mode=max on a multi-stage Dockerfile writes every stage, which is why teams often set a registry lifecycle rule on the cache tag.
Branch strategy is the other decision the reference encodes. A single shared tag means concurrent branches overwrite each other's cache. Giving each branch its own tag and reading from both the branch tag and the default branch keeps the two apart, since cache-from may be given more than once:
cache-from: |
type=registry,ref=ghcr.io/${{ github.repository }}:cache-${{ github.ref_name }}
type=registry,ref=ghcr.io/${{ github.repository }}:cache-main
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:cache-${{ github.ref_name }},mode=maxAccess control follows registry permissions. Writing the cache needs push rights on the repository, reading it needs pull rights, and a workflow triggered by a fork pull request usually has read-only credentials. Passing ignore-error=true on cache-to lets those runs read the cache and skip the export instead of failing the build.
Related Terms
- Inline cache, the same metadata embedded in the pushed image: what
type=inlinerecords, and why it cannot describe intermediate stages. - Buildx cache backends in GitHub Actions: the registry, GitHub Actions, local, and inline backends side by side, with the tradeoffs for each.
- The difference between cache-from and cache-to: why a build that sets one flag looks cached while never populating the cache.
- Docker registry cache backend reference: the upstream parameter list and media type details.
- WarpBuild Docker builder documentation: builder profiles and the actions that point a workflow at a builder outside the job.
- WarpBuild caching documentation: cache action inputs, key matching, and entry expiry.
- WarpBuild pricing: per minute rates by runner type and by builder size.
FAQ
Where does a registry cache actually live?
In a container registry, as its own tag next to the application image. The cache tag holds the exported layer blobs plus a BuildKit cache config blob that records which build step produced which layer, so it is pulled like any other artifact and never runs as a container.
Why does my registry cache export fail with the default builder?
Docker documents the registry backend as unsupported on the default docker driver. Create a builder on the docker-container, kubernetes, or remote driver first, which is what docker/setup-buildx-action does when it creates a docker-container builder for the job.
What does mode=max change?
The default mode=min exports only the layers of the final stage of the resulting image. mode=max exports the layers of every stage, including build stages whose output is copied forward, so a multi-stage Dockerfile can reuse its compile stage on the next run.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.