Incremental Docker Builds
Learn more about incremental Docker builds
Overview
Blacksmith runners support incremental docker builds with a one-line code change.
With this feature, Docker builds in your GitHub Action workflows can reuse cached layers from previous runs, and only rebuild the layers that have changed. This can significantly reduce the time it takes to build your Docker image. Our customers have reported 2x to 40x improvements in build times due to incremental Docker builds.
Quickstart
To enable incremental Docker builds with Blacksmith runners, all you need to do is change the following line in your GitHub Actions workflow file:
The useblacksmith/build-push-action
action is a drop-in replacement for the docker/build-push-action
action and any inputs you provide to the docker/build-push-action
action will work with the useblacksmith/build-push-action
action.
Once you make this switch, the first Docker run will be an uncached run. Every subsequent run will have the hydrated layer cache mounted into your runners, so you should see several build steps cached from previous runs.
With Blacksmith’s incremental docker builds, any external caching that was configured with cache-from
and cache-to
directives can now be removed. There is no longer a need to upload and download artifacts since your runner will already be hydrated with the relevant Docker layer cache.
How it works
How caching works in Docker builds
When you do a Docker build, each step in your Dockerfile creates a new layer in your Docker image. Without caching, when you make a change to your Dockerfile, Docker will rebuild all the layers in the image, even if only one layer has changed. This can be slow, especially for large Docker images.
However, with caching, Docker can reuse layers from previous builds instead of rebuilding them from scratch. Docker will only rebuild from the layer that has changed and use the cached layers for the rest of the image.
How Blacksmith runners enable incremental Docker builds
When a GitHub Action job uses the useblacksmith/build-push-action
, a hydrated layer cache from previous runs will be mounted into the runner.
The Docker build triggered by the build-push-action will subsequently be able to use the cached layers, instead of rebuilding everything from scratch.
At the end of the job, the runner will commit its changes to the layer cache for future runs.
The Docker layer cache is shared by all runners in a repository, in your organization. In case of several concurrent Docker build, it may take a few runs until all the builds have their layers committed to the cache. This is in keeping with the Last Write Wins (LWW) policy we enforce in the face of concurrent committers.
Multi-platform builds (Coming soon)
The useblacksmith/build-push-action
action supports multi-platform builds. All you need to do is specify the platforms you want to build for in the platforms
input.
Blacksmith will automatically spawn native builders for each platform eliminating the need for any emulation.
Each image will be built on a native builder i.e. the amd64 Docker build on a blacksmith-8vcpu-ubuntu-2204
and the arm64 build on a blacksmith-8vcpu-ubuntu-2204-arm
runner.
For ARM builds, this avoids needing to use QEMU to emulate ARM on an amd64 runner, which can be extremely slow.
Security
Incremental Docker builds execute within the same runners that process your GitHub Actions workflows. This means they automatically inherit all security protections and isolations that are detailed in our security documentation.
The BuildKit daemon in each runner (buildkitd
) that powers Docker builds, runs exclusively on a local Unix socket and is not exposed to the public internet.
The Docker layer cache for each repository is stored in a secure Ceph cluster. Every runner gets an ephemeral authentication token that allows it to request and commit artifacts. The runners do not have persistent credentials to the Ceph cluster or direct access to artifacts in the cluster. The Ceph cluster is configured with object-level access controls.