A fresh runner has an empty build cache, so every pipeline run rebuilds every layer unless the cache is exported somewhere the next run can reach.
- uses: docker/setup-buildx-action@v2
- uses: docker/build-push-action@v3
with:
context: .
push: true
tags: registry/app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
mode=max exports intermediate layers rather than only the final image, which is what helps a multi-stage build and also stores considerably more. The cache has a size limit with least-recently-used eviction, so a repository building several images can find them evicting each other — at which point the build times become inconsistent in a way that looks like a runner problem.