> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blacksmith.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# 40x Faster Docker Builds

> Blacksmith optionally caches your Docker layers to speed up your workflows

## 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](https://www.linkedin.com/feed/update/urn:li:activity:7325661286027919361/) 2x to 40x improvements in build times due to this change.

```yml Diff Example icon="code" lines theme={"system"}
   name: Set up Docker Buildx
   uses: docker/setup-buildx-action@v4 # [!code --]
   uses: useblacksmith/setup-docker-builder@v1 # [!code ++]

   name: Build and Push Docker Image
   uses: docker/build-push-action@v7 # [!code --]
   uses: useblacksmith/build-push-action@v2 # [!code ++]
   with:
    push: true
    tags: user/app:latest
    cache-from: type=registry,ref=user/app:latest # [!code --]
    cache-to: type=inline # [!code --]
```

Any external caching that was configured with cache-from and cache-to directives can now be removed. 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.

<Info>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.</Info>

## 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.

```yml Diff Example icon="code" lines theme={"system"}
  uses: useblacksmith/setup-docker-builder@v1 # [!code ++]
  # ... Docker commands in your workflow ... 
```

### Capping the cache size

By default, the Docker layer cache grows as new layers are committed. For repositories with large or frequently changing images, this can cause the cache to expand beyond what you actually need. You can bound it with the `max-cache-size-mb` input on `useblacksmith/setup-docker-builder`.

When set, the builder prunes the BuildKit cache at the end of the job, retaining at most the specified number of megabytes. Pruning removes the least used layers first, breaking ties by the oldest last-accessed timestamp, so the most recently and frequently used layers stay hot while older, rarely touched ones are discarded. If the input is not set, no pruning is performed and the cache grows unbounded (subject to the sticky disk's capacity and eviction policy).

```yml Example icon="code" lines theme={"system"}
- uses: useblacksmith/setup-docker-builder@v1
  with:
    max-cache-size-mb: "409600" # retain up to 400 GB of build cache
- uses: useblacksmith/build-push-action@v2
  with:
    push: true
    tags: user/app:latest
```

The value is specified in megabytes (for example, `10240` for 10 GB, `409600` for 400 GB). Choose a size that comfortably fits your working set of layers — setting it too low will cause frequently used layers to be evicted and rebuilt on subsequent runs.

### How it works

Under the hood, your Docker layer caches are stored on [sticky disks](/blacksmith-caching/dependencies-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:

1. The `setup-docker-builder` action configures a buildx builder with access to cached layers from previous runs
2. The `build-push-action` then uses this builder to run your Docker build, leveraging the cached layers instead of rebuilding everything from scratch
3. 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.

The Docker layer cache is shared by all runners in a repository, in your organization.
In case of several concurrent Docker builds, 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

#### 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.

```yml Diff Example icon="code" lines theme={"system"}
jobs:
  build:
    strategy:
      matrix:
        platform: [amd64, arm64]
        include:
          - platform: amd64
            runner: blacksmith-8vcpu-ubuntu-2204 # [!code ++]
            docker_platform: linux/amd64
          - platform: arm64
            runner: blacksmith-8vcpu-ubuntu-2204-arm # [!code ++]
            docker_platform: linux/arm64
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      
      - name: Setup Docker Builder
        uses: useblacksmith/setup-docker-builder@v1 # [!code ++]
      
      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      
      - name: Build and push Docker image
        uses: useblacksmith/build-push-action@v2 # [!code ++]
        with:
          push: true
          tags: user/app:${{ matrix.platform }}
          platforms: ${{ matrix.docker_platform }}
```

This approach runs each architecture build on its native hardware - the amd64 build runs on `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:

```yml Diff Example icon="code" lines theme={"system"}
jobs:
  build:
    # ... matrix strategy builds from above ...
  
  merge-manifests:
    needs: build
    runs-on: blacksmith # [!code ++]
    steps:
      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      
      - name: Create and push multi-arch manifest
        run: |
          docker manifest create user/app:latest \
            user/app:amd64 \
            user/app:arm64
          docker manifest push user/app:latest
```

For registries that require explicit annotation of architectures:

```yml Diff Example icon="code" lines theme={"system"}
- name: Create and push annotated manifest
  run: |
    docker manifest create user/app:latest \
      user/app:amd64 \
      user/app:arm64
    docker manifest annotate user/app:latest user/app:amd64 --arch amd64
    docker manifest annotate user/app:latest user/app:arm64 --arch arm64
    docker manifest push user/app:latest
```

#### Coming soon: Native multi-platform support

In the future, the `useblacksmith/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.

```yml Diff Example icon="code" lines theme={"system"}
jobs:
  build:
    runs-on: blacksmith-8vcpu-ubuntu-2204 # [!code ++]
    steps:
    ...
    - name: Build and push Docker image
      uses: useblacksmith/build-push-action@v2 # [!code ++]
      with:
        platforms: linux/amd64,linux/arm64
```

When this feature is available, 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) 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](https://www.blacksmith.sh/security).

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](https://www.blacksmith.sh/pricing).

## FAQ

<AccordionGroup>
  <Accordion title="What is the eviction policy?">
    Docker layer caches are stored on [sticky disks](/blacksmith-caching/dependencies-sticky-disks), with a separate sticky disk created per unique Dockerfile in a repository. 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.
  </Accordion>

  <Accordion title="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](/blacksmith-caching/docker-builds#not-using-the-dockerbuild-push-action).
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>
