Effective BuildKit cache in GitHub Actions
Docker BuildKit supports cache. It would reduce time to build an image, especially for multistage Dockerfile.
This article explains efficient cache strategy in pull request based development flow.
Problem to solve
You can import and export cache by passing the following config to docker/build-push-action:
- uses: docker/build-push-action@v2
with:
cache-from: type=registry,ref=IMAGE
cache-to: type=registry,ref=IMAGE,mode=max
It always imports the cache from registry and exports the cache to registry.
In pull request based development flow, cache is overwritten by pull requests and it may cause cache miss. For example,
- Initially
main
branch is built and cache is set to A - When pull request B is opened, cache is hit and overwritten to B
- When pull request C is opened, cache is missed and overwritten to C
- When pull request B is merged into
main
branch, cache is missed and overwritten to D
Solution
We can improve cache efficiency by the following design:
- Cache always points to the base branch
- Don’t export cache on a pull request
This diagram shows relationship of commit and cache.
pull_request
event
When a pull request is opened, it need to import the cache of base branch. Do not export the cache to prevent cache pollution. For example,
cache-from: type=registry,ref=IMAGE:main
cache-to:
push
event of branch
When a branch is pushed, it need to import and export the cache of pushed branch. For example,
cache-from: type=registry,ref=IMAGE:main
cache-to: type=registry,ref=IMAGE:main,mode=max
Other events
Otherwise, it need to import the cache of triggered branch. Do not export the cache to prevent cache pollution. For example,
cache-from: type=registry,ref=IMAGE:main
cache-to:
How to use
I wrote int128/docker-build-cache-config-action to generate a cache config. You can use it with docker/build-push-action as follows:
- uses: docker/metadata-action@v3
id: metadata
with:
images: ghcr.io/${{ github.repository }}
- uses: int128/docker-build-cache-config-action@v1
id: cache
with:
image: ghcr.io/${{ github.repository }}/cache
- uses: docker/build-push-action@v2
id: build
with:
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: ${{ steps.cache.outputs.cache-from }}
cache-to: ${{ steps.cache.outputs.cache-to }}
It sets the cache config as follows:
- On pull request, only
cache-from
is set to the base branch - On push of branch, both
cache-from
andcache-to
are set - Otherwise, only
cache-from
is set to the triggered branch
Conclusion
This article explains efficient cache strategy in pull request based development flow.
You can use int128/docker-build-cache-config-action to generate a config for docker/build-push-action.
Enjoy DevOps!