Overview
Now that you have a Blacksmith runner, you can take advantage of our NVMe-backed cache to persist your Docker layers across CI runs. To enable Docker layer caching, you’ll use two Blacksmith actions in your GitHub Actions workflow file. This allows your Docker builds to reuse cached docker layers from previous runs, and only rebuild the layers that have changed. Our customers have reported 2x to 40x improvements in build times due to this change.Diff Example
type=gha, type=registry, type=inline) serialize layer blobs on every build and never include the contents of RUN --mount=type=cache directories; Blacksmith keeps the builder’s state (layers and cache mounts) on a persistent disk instead, so there is nothing to import or export: your builds skip the latency of downloading and uploading the entire cache blob on every run. 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.
The required cache-key input identifies the layer cache this build uses; builds with the same key share cached layers across runs. A good default is the Dockerfile being built (e.g., services/api/Dockerfile); see scoping guidance below for when builds should share a key.
When using
useblacksmith/build-push-action without useblacksmith/setup-docker-builder, the runner will use the default builder configured in your environment. However, this builder will not leverage Blacksmith’s Docker layer caching nor will it report Docker analytics to the Blacksmith control plane.Basics
Not using the docker/build-push-action?
If you’re not using the docker/build-push-action in your workflow, but are instead calling Docker commands directly or are using the docker/bake-action, you can still cache your Docker layers by setting up a Blacksmith builder before interacting with Docker.
This builder will be hydrated with the layer cache from previous runs and will commit the updated layer cache at the end of the job.
Diff Example
Garbage collection
The layer cache is kept in check by BuildKit’s native garbage collection with a time-based policy: layers that haven’t been used for 8 days are automatically cleaned up, while actively used layers are kept regardless of total cache size. No configuration is needed.Getting the most out of the layer cache
A persistent builder makes caching fast, but how much of each build is cached still depends on your Dockerfile and workflow. The following practices have the largest measured impact:Scope the cache to one build workload
Builds only benefit from sharing a cache when they actually reuse each other’s layers. Unrelated images building against one shared cache evict each other’s state and can be slower than not caching at all. The requiredcache-key input on setup-docker-builder makes this scope explicit.
The unit of scoping is the builder: one cache backs everything a builder instance builds. When each job builds one Dockerfile — the common case — that means one key per Dockerfile, and the Dockerfile’s path is a good key. When a single job builds several related Dockerfiles behind one builder (variants of one image, a base image plus images derived from it), they share that job’s key; name it after the image set rather than one Dockerfile. Related images that reuse each other’s layers can share a key on purpose — it is genuinely unrelated builds that should be kept on separate keys. Use the same key for the same workload across workflows so, for example, release builds reuse layers from CI builds of the same image.
Order layers by change frequency
A changed instruction invalidates its layer and every layer after it. Put expensive, stable steps early and frequently changing content late. The most common form is manifest-first ordering: copy the dependency manifest, install dependencies, and only then copy the source, so the install layer is only rebuilt when the manifest changes.Example
Use cache mounts for package managers and compilers
RUN --mount=type=cache directories persist package-manager and compiler state (Go’s build cache, cargo’s registry and target/, pip’s wheels, pnpm’s store) across builds, so a step that does have to re-run is incremental instead of from scratch. Because Blacksmith persists the whole builder disk, these mounts survive between CI jobs; with export-based backends (type=gha, type=registry) they are always empty on a fresh runner.
Example
Push from the builder instead of loading into the daemon
If your job pushes the image to a registry, usepush: true on the build action rather than load: true followed by docker push. load: true exports the image into the local Docker daemon and then reads it back for the push, handling every byte twice; push: true streams the image from the builder straight to the registry.
Example
docker buildx imagetools inspect user/app:latest instead of loading it locally.
How it works
Under the hood, your Docker layer caches are stored on sticky disks.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 cache your Docker layers
When a GitHub Action job uses the Blacksmith Docker actions, the process works as follows:- The
setup-docker-builderaction configures a buildx builder with access to cached layers from previous runs - The
build-push-actionthen uses this builder to run your Docker build, leveraging the cached layers instead of rebuilding everything from scratch - At the end of the job, the runner commits its changes to the layer cache for future runs. This commit only runs if no other steps in the job have failed or been canceled.
/var/lib/docker) used by docker pull, docker run, service containers, and container: jobs.
Multi-platform builds
Current approach: Using a matrix strategy
You can build multi-platform Docker images on Blacksmith by using GitHub Actions matrix strategy. This approach leverages Blacksmith’s native runners for each architecture to avoid the performance penalties of emulation.Diff Example
blacksmith-8vcpu-ubuntu-2204 and the arm64 build runs on blacksmith-8vcpu-ubuntu-2204-arm. Each image is pushed with its own tag that includes the architecture.
For ARM builds, this avoids needing to use QEMU to emulate ARM on an amd64 runner, which can be extremely slow.
Merging images into a multi-arch manifest
After building separate images for each architecture, you can merge them into a single multi-arch manifest using Docker’s manifest commands:Diff Example
Diff Example
Coming soon: Native multi-platform support
In the future, theuseblacksmith/build-push-action action will support multi-platform builds natively. You’ll simply need to specify the platforms you want to build for in the platforms input, and Blacksmith will automatically spawn native builders for each platform, eliminating the need for the matrix strategy shown above.
Diff Example
blacksmith-8vcpu-ubuntu-2204 and the arm64 build on a blacksmith-8vcpu-ubuntu-2204-arm runner) and automatically merged into a single multi-arch manifest.
Security
Docker layer caching executes 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.
Pricing
Docker build caching is powered by sticky disks and is charged at the same rate of $0.50/GB/mo. For pricing details, please visit our pricing page.FAQ
What is the eviction policy?
What is the eviction policy?
Docker layer caches are stored on sticky disks, with a separate sticky disk created per
cache-key. The sticky disk is automatically evicted after 7 days of inactivity. Each Docker build updates the “last used” timestamp on the sticky disk, so as long as you run at least one Docker build within a 7-day window, your layer cache will remain available.Didn't you support a setup-only option?
Didn't you support a setup-only option?
Yes,
useblacksmith/build-push-action@v1 has been deprecated. Please move to the newer approach laid out above. If you were using setup-only refer to this section.How can I monitor my usage?
How can I monitor my usage?
Users can login to the Blacksmith dashboard and navigate to the
Usage & Billing page to get a breakdown of their current usage.