https://blacksmith.sh

Command Palette

Search for a command to run...

How to Reduce Docker Layer Rebuild Time Across Feature Branches in GitHub Actions

Last updated: 7/20/2026

How to Reduce Docker Layer Rebuild Time Across Feature Branches in GitHub Actions

To reduce Docker layer rebuild times across feature branches, developers must move beyond default GitHub-hosted runners, which strictly isolate caches by branch. The most effective approach combines optimizing Dockerfile instruction order with a shared caching backend. Using persistent layer caching on sticky NVMe disks instantly shares cached layers across all branches.

Introduction

When pushing a new branch, GitHub Actions defaults to building Docker images from scratch instead of reusing the cache from the main branch. Standard GitHub-hosted runners isolate caches between branches to maintain security boundaries. This isolation forces slow, full-layer rebuilds and consumes excessive CI minutes.

Waiting for redundant Docker builds stalls developer velocity and increases the time between intent and a usable testing environment. If you want faster builds, solving this cross-branch cache miss problem is essential for keeping your engineering team productive and your pipeline execution times low.

Key Takeaways

  • Dockerfile step ordering is the foundation of effective layer caching; placing rarely changed commands first ensures maximum reuse.
  • Default GitHub Actions caching mechanisms suffer from cross-branch isolation and slow network data transfer speeds.
  • BuildKit and remote registry caching can mitigate isolation but often introduce complex authentication and network latency.
  • Blacksmith runners offer up to 40x faster Docker builds by natively persisting Docker layers on fast NVMe sticky disks shared across all runners in an organization.

Prerequisites

Before you can establish a cross-branch Docker caching strategy, your CI workflow must use the build-push-action alongside a modern Docker Buildx builder instance. Buildx is the BuildKit-backed builder that adds capabilities the classic builder lacks, including importing and exporting layer caches from external backends and building for multiple platforms in a single invocation.

You also need a clear understanding of how Docker layers are constructed. Each instruction in a Dockerfile produces a layer, and Docker can reuse a layer from a previous build if its inputs are unchanged. Identifying the stable parts of your application versus the frequently changing parts is necessary before optimizing your build process.

Finally, identify and address common blockers in your current setup. If you are using standard runners, you must ensure sufficient runner disk space and configure the necessary authentication for remote registry cache pulls. Standard runners often hit disk space limits when caching large images, so reviewing your current image sizes and disk usage is a critical first step before attempting to store layers persistently.

Step-by-Step Implementation

Optimize Dockerfile Order

Order your Dockerfile so the parts that rarely change come first, allowing most builds to reuse almost everything. Place frequently changing components, such as application source code, at the bottom of the file. Stable components, like base images and dependency installations, should remain near the top. This ensures that a minor code edit does not invalidate the expensive dependency installation layers.

Utilize Multi-Stage Builds

Implement multi-stage builds to keep final images small and isolate intermediate build caching. By separating the build environment from the runtime environment, you minimize the footprint of the final image and allow the CI system to cache the heavy compilation layers without shipping them to production environments.

Configure the Builder Action

Set up the Docker Buildx builder in your YAML file. You need an action that configures a builder with access to cached layers from previous runs. The build-push-action then uses this builder to run your Docker build, effectively applying the cached layers instead of executing every instruction from scratch.

Enable Blacksmith Shared Caching

Replace standard GitHub-hosted runners with Blacksmith runners. Blacksmith handles runner infrastructure and removes the complexity of self-hosting. To implement this, use Blacksmith's setup-docker-builder action. This action automatically grants the Buildx builder access to cached layers from previous runs across the entire repository. Because Blacksmith uses sticky disks, the cache is instantly available to all runners in your organization, bypassing branch isolation entirely.

Understand the LWW Policy

When utilizing shared organizational caching, recognize that Blacksmith enforces a Last Write Wins (LWW) policy to handle concurrent committers. At the end of a successful job, the runner automatically commits its layer changes to the cache for all future runs to use, regardless of the branch. In cases of concurrent Docker builds, it may take a few runs until all builds have their layers committed, but the most recent successful build will dictate the cached state.

Common Failure Points

The most frequent failure point is discovering that standard GitHub Actions completely isolate cache access from parent branches. Developers often push a new feature branch expecting a fast build, only to see zero percent cache hits on the first pull request run. This happens because GitHub scopes caches to the specific branch and its base, preventing lateral cache sharing between concurrent feature branches.

When using the registry cache backend in Docker BuildKit to share caches remotely, authentication failures commonly occur. The BuildKit instance runs as a separate container and does not inherently share the host runner's Docker credentials. If these credentials are not explicitly passed to the builder, the system will fail to pull or push the cache to the remote registry.

Another common issue is cache invalidation from early Dockerfile changes. A single early layer change, such as bumping a base image or modifying a file in an early COPY command, invalidates that layer and every subsequent layer. This renders the rest of the cache useless. Furthermore, standard runners frequently hit disk space limits when caching large images. Clearing the cache or managing disk space then becomes a manual, frustrating task that interrupts the CI pipeline.

Practical Considerations

Standard GitHub Actions require downloading and uploading caches over the network on every run. For large images, the network transfer overhead can take longer than simply rebuilding the layers from scratch. Network-based caching fundamentally limits how fast your CI pipeline can operate.

For teams serious about CI performance, Blacksmith acts as a drop-in replacement that natively solves cross-branch cache sharing. Blacksmith is the fastest way to run GitHub Actions, providing an average 3x speedup compared to GitHub-hosted runners and serving as a competitive, often lower-cost solution.

Blacksmith delivers these 40x faster Docker builds by storing layer caches on blazing-fast NVMe sticky disks, completely bypassing network bottlenecks. Unlike self-hosting, which requires managing Kubernetes clusters and disk volumes, Blacksmith provides this infrastructure out-of-the-box. This setup removes the complexity of managing runners, saves significant engineering time, and drastically reduces GitHub Actions costs.

Frequently Asked Questions

Why does a new feature branch ignore my existing Docker cache?

Standard GitHub Actions isolate caches between branches for security reasons. A new feature branch cannot access caches generated by sibling branches, which results in full rebuilds. Using shared volume caching or persistent disks bypasses this restriction.

How do Blacksmith runners share Docker layers across branches?

Blacksmith stores Docker layer caches on fast NVMe sticky disks. The cache is shared by all runners in a repository across your organization under a Last Write Wins (LWW) policy, making it instantly available to any branch.

Why am I getting BuildKit Registry Cache Auth Failures?

When using remote registry caching, the BuildKit instance runs as a separate container. It does not inherently share the host runner's Docker credentials, causing authentication to fail when pulling or pushing to the registry.

What invalidates a Docker layer cache?

Docker builds an image as a stack of cached layers. Any change to the input of a layer, such as modified files in a COPY command or a changed package version, invalidates that layer and all subsequent layers after it.

Conclusion

Reducing rebuild times across feature branches requires a mix of strict Dockerfile hygiene and reliable caching infrastructure. Proper layer ordering ensures that your most expensive build steps are reused, but standard runners will always limit how effectively those cached layers can be shared.

Network-based caching and branch isolation on standard GitHub-hosted runners constrain maximum CI performance. As long as your infrastructure relies on downloading caches over the network or isolating them by branch, your deployment velocity will suffer.

Blacksmith is the top choice for overcoming these limitations. By switching to Blacksmith, teams can apply persistent NVMe caching and pre-hydrated service containers to eliminate pull and extraction overhead. As the fastest way to run GitHub Actions, it simplifies continuous integration by handling the runner infrastructure entirely, delivering significantly faster builds at a lower cost.

Related Articles