Docker BuildKit Cache Not Working with GitHub Actions
When using Docker BuildKit in a GitHub Actions workflow, the build cache does not seem to persist between jobs or workflow runs, resulting in slow builds every time. The --build-arg BUILDKIT_INLINE_CACHE=1
and cache-from
options are set, but caching is not effective. How can I enable persistent Docker BuildKit cache in GitHub Actions?
Solution
By default, GitHub Actions runners are ephemeral and do not persist Docker layer cache between jobs or workflow runs. To enable persistent Docker BuildKit cache, you need to use the actions/cache action to store and restore the /tmp/.buildx-cache
directory. Here is an example:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: false
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
This setup will significantly speed up your Docker builds in GitHub Actions.
Alternative #1
I've been optimizing Docker builds in CI for years, and sometimes the local cache approach above doesn't work as expected. One alternative that's been more reliable for me is using Docker Hub as a cache backend.
Here's how to set it up:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push with cache
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: your-username/your-image:latest
cache-from: type=registry,ref=your-username/your-image:buildcache
cache-to: type=registry,ref=your-username/your-image:buildcache,mode=max
The key differences:
- Uses
type=registry
instead oftype=local
- Pushes cache to Docker Hub as a separate tag
- More reliable across different runners and environments
This approach works better in multi-branch scenarios where you want to share cache across different branches.
Alternative #2
Another approach I've found effective is using GitHub Container Registry (GHCR) as your cache backend, which is often faster and more reliable than Docker Hub for GitHub Actions.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push with GHCR cache
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:cache
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:cache,mode=max
platforms: linux/amd64,linux/arm64
Benefits of GHCR:
- Faster in GitHub Actions (same infrastructure)
- No rate limits like Docker Hub
- Automatic cleanup of old cache layers
- Better integration with GitHub's ecosystem
I've seen 50-70% faster builds with this approach compared to local cache.
Alternative #3
If you're dealing with multi-stage builds or complex dependencies, you might want to consider a hybrid approach that combines multiple cache strategies.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers (local)
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build with hybrid cache
uses: docker/build-push-action@v5
with:
context: .
push: false
cache-from: |
type=local,src=/tmp/.buildx-cache
type=registry,ref=ghcr.io/${{ github.repository }}:cache
cache-to: |
type=local,dest=/tmp/.buildx-cache
type=registry,ref=ghcr.io/${{ github.repository }}:cache,mode=max
build-args: |
BUILDKIT_INLINE_CACHE=1
This approach:
- Uses local cache for immediate speed
- Falls back to registry cache if local cache is missing
- Provides redundancy and better cache hit rates
- Works well for both development and production builds
I've used this in production environments where build reliability is critical.