docker buildx Cache Backends: Which to Use in CI
A CI runner starts with an empty Docker cache every time. buildx cache backends persist layers between runs, and choosing the wrong one silently evicts every other cache in your repository.
Docker layer caching works locally because your daemon keeps layers between builds. A CI runner has no such history, so every layer rebuilds unless you configure an external cache backend.
The backends differ mainly in where they store data and what that storage costs you. The default choice, type=gha, is convenient and shares a hard 10 GB budget with every other cache in the repository.
The backends
| Backend | Stored in | Best for | Limitation |
|---|---|---|---|
type=gha | GitHub Actions cache | Small and medium images | Shares the 10 GB repository cap |
type=registry | A container registry | Large images, cross-repo sharing | Registry storage cost and auth |
type=local | Runner filesystem | Self-hosted with persistent disk | Useless on ephemeral runners |
type=inline | Embedded in the image | Simple single-stage builds | Final stage only, no intermediates |
type=s3 | S3 or compatible | Large scale, own storage | More configuration, credentials |
mode=min against mode=max
# min (default): only the final stage layers
cache-to: type=gha,mode=min
# max: intermediate stages too. What makes multi-stage builds benefit
cache-to: type=gha,mode=maxRegistry cache for anything substantial
- 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: .
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max
permissions:
contents: read
packages: write # required or the cache push is rejectedVerify the cache is hitting
docker buildx build --progress=plain . 2>&1 | grep -E "CACHED|importing cache" | head -20
# CACHED on the dependency install layer means it works.
# No CACHED lines at all means cache-from is not matching.Frequently asked questions
Why is Docker not caching layers in CI?
cache-from and cache-to.Should I use type=gha or type=registry?
type=gha for small to medium images where you will not approach the 10 GB Actions cache cap. type=registry for large images, cross-repository sharing, or when you already push to a registry.What does mode=max do?
Does the Docker layer cache count against the Actions cache limit?
type=gha. It shares the 10 GB per-repository budget with every other cache, so a large layer cache can evict your dependency caches and slow down unrelated jobs.