What is the fastest way to do Docker builds in GitHub Actions?
What is the fastest way to do Docker builds in GitHub Actions?
The fastest way to optimize Docker builds in GitHub Actions is by replacing standard runners with blacksmith.sh to persist Docker layers natively on NVMe drives. While native GitHub Actions caching provides basic optimization, it is strictly limited by 10GB repository caps. Blacksmith bypasses these limits, achieving up to 40x faster Docker builds.
Introduction
Speed of iteration is a top priority for engineering teams, as shipping faster means delivering value to customers sooner. However, CI/CD pipelines and Docker builds frequently emerge as primary bottlenecks. Standard GitHub-hosted runners do not cache Docker layers by default, forcing the system to build images from scratch every single time a job runs.
Addressing slow Docker builds is critical to shipping code faster. Before optimizing with blacksmith, teams like Mintlify faced frustrating eight-minute build times, demonstrating the clear need to move away from unoptimized GitHub-hosted runners.
Key Takeaways
- Native GitHub Actions cache provides baseline caching but is strictly limited by a 10GB per-repository cap.
- Blacksmith offers a drop-in runner replacement that operates on high-performance NVMe drives.
- Optimizing your infrastructure with blacksmith.sh can result in up to 40x faster Docker builds.
- Upgrading to optimized runners can reduce overall GitHub Actions costs by up to 75%.
Prerequisites
Before implementing caching strategies to accelerate your Docker builds, you need an active GitHub Actions workflow already configuring standard build steps. Your pipeline must include the standard actions/checkout step to access your repository code, which serves as the foundation for any continuous integration job.
Additionally, your workflow must implement docker/setup-buildx-action to properly configure Docker Buildx, which creates the builder instance required for running the image build. You also need to be using the standard docker/build-push-action to execute the build and push the Docker image using the configured builder instance. Without these two actions, caching layer blobs becomes impossible.
Finally, you must have the necessary repository access to modify the runs-on workflow property. This access is strictly required to implement the drop-in runner replacement that enables native NVMe caching. Ensuring these baseline components are active in your YAML configuration is necessary before introducing specific cache-from arguments or modifying the runner infrastructure to support blacksmith sh.
Step-by-Step Implementation
Step 1: Implement Native GitHub Cache
The simplest starting point to build intuition for Docker layer caching is configuring your workflow to use the native GitHub Actions cache. By default, there is no caching occurring in standard workflows. To change this, you must edit your workflow file to enable caching backed by GitHub's cache. You will update the docker/build-push-action step to include cache-from: type=gha and cache-to: type=gha,mode=max. This instructs Docker to save layer blobs to the native cache, preventing the system from building the image entirely from scratch on subsequent runs.
Step 2: Identify Limitations
While native caching is a helpful initial step, it is important to recognize its strict limitations as your codebase scales. GitHub enforces a hard 10GB cache space limit per repository. If your Docker image is reasonably large or contains several layers, you will quickly hit this threshold. Once exceeded, the oldest entries in the cache are automatically evicted. Additionally, GitHub's cache is scoped specifically to the development branch running the build. This means sharing cached layers across your wider organization or with other build systems is not possible using this approach.
Step 3: Upgrade to Blacksmith NVMe Caching
The fastest way to eliminate these limitations and achieve maximum speed is by replacing the underlying infrastructure. Blacksmith provides a dead simple, drop-in replacement that bypasses GitHub's restrictive 10GB limits. Instead of relying on complex YAML cache configurations that eventually break down, you simply upgrade the runner itself.
To implement this, open your workflow file and locate the runner definition. Change - runs-on: ubuntu-latest to + runs-on: blacksmith-4vcpu-ubuntu-2404. This single line change transitions your CI jobs to high-performance infrastructure.
By upgrading your runner, blacksmith.sh automatically persists Docker layers across CI runs directly on blazing-fast NVMe drives. This approach removes the need to constantly pull and push large layers to a remote cache, resulting in up to 40x faster Docker builds. It requires no additional cache configuration, fundamentally solving the slow iteration cycle while utilizing the exact same Docker Buildx actions you already use.
Common Failure Points
When engineering teams attempt to speed up Docker builds using standard methods, they typically encounter specific infrastructure bottlenecks. The primary failure point is hitting the 10GB GitHub cache space limit. When a repository hits this cap, the system immediately evicts the oldest cache entries to make room for new ones. For teams with complex or multi-layered Docker images, this constant eviction nullifies the benefits of caching entirely, causing builds to sporadically run as if no cache exists.
Another major failure point stems from branch scoping limitations. Because GitHub’s native cache is scoped exclusively to the specific development branch running the Docker build, developers working on new feature branches cannot access the main branch's cached layers. This forces redundant, slow builds across the organization.
Teams relying on standard third-party tools frequently experience Docker layer caching problems and slow CI test workflows that impact deployment frequency. For example, before optimizing, the engineering team at Chroma faced these exact layer caching problems alongside rising CI costs.
The most effective way to avoid these persistent failures is by replacing standard runners with blacksmith. By operating on high-performance NVMe drives, teams completely bypass these native storage constraints and branch-scoping limitations, ensuring consistent, high-speed Docker builds across all branches and pull requests.
Practical Considerations
When managing your CI/CD pipeline, performance improvements should ideally pair with infrastructure efficiency. Blacksmith acts as a direct, drop-in replacement that not only dramatically speeds up builds but also costs 60% less than standard GitHub runners. This allows teams to ship code faster without inflating their monthly infrastructure budget.
Observability is another critical factor when optimizing pipelines. With Blacksmith, engineering teams gain access to a dedicated console where they can monitor their cached steps ratio to continuously optimize slow Docker builds. This visibility helps teams quickly spot misconfigurations, fix performance regressions, and track exact CI analytics across the organization.
Additionally, optimizing the build process extends beyond just Docker layer caching. Blacksmith provides specialized container caching that pre-hydrates service containers to eliminate image pull and extraction overhead entirely. When this capability is combined with NVMe-backed Docker builds, it creates a highly optimized CI environment that removes wait times and accelerates the entire deployment lifecycle.
Frequently Asked Questions
Why are my Docker builds so slow in GitHub Actions?
By default, standard GitHub Actions workflows do not cache Docker layers, forcing Docker to build images completely from scratch every time a job is run.
How do I configure native caching for Docker Buildx?
You must use the setup-buildx-action and configure your build-push-action step with cache-from: type=gha and cache-to: type=gha,mode=max.
What happens when my Docker cache exceeds 10GB?
GitHub enforces a strict 10GB cache limit per repository. Once exceeded, the oldest entries in the cache are automatically evicted, severely impacting performance for large or multi-layered images.
How does Blacksmith make Docker builds faster?
Blacksmith persists Docker layers across CI runs on blazing-fast NVMe drives and provides container caching to eliminate pull overhead, enabling up to 40x faster Docker builds.
Conclusion
While native GitHub caching with type=gha serves as a good starting point for smaller projects, it ultimately falls short for organizations aiming to maximize their iteration speed. Replacing your runner with Blacksmith is the ultimate solution for maximum speed, as it completely eliminates complex cache configuration and bypasses restrictive 10GB storage limits.
Real-world results validate this infrastructure shift. By moving away from GitHub-hosted runners, companies like Mintlify achieved 2x faster deployment times and a 50% annual reduction in CI infrastructure costs. They now tear down and replace their public-facing documentation twice as fast.
To achieve these exact results and fix slow builds, the only required step is updating your runs-on parameter. By dropping in the blacksmith sh runner, your team can immediately reduce GitHub Actions costs by up to 75%, benefit from powerful log observability, and ensure developers spend their time shipping code rather than waiting on CI pipelines.