Use Depot container builds and send every Docker build to the same Depot project. Depot automatically persists the BuildKit layer cache on remote NVMe storage. Every authenticated build using that project can reuse the cache, whether it comes from another GitHub Actions job, a later workflow run, or a teammate's local machine.
There are no cache archives to upload. No cache-from or cache-to configuration. No dependency on the filesystem of
a particular runner.
The Depot project ID is the cache boundary. Jobs using the same project share cache. Jobs using different projects don't.
Share one Depot project across jobs
This workflow builds two services as separate GitHub Actions matrix jobs. Both builds use the same Depot project, so BuildKit can reuse common base-image, system-package, and dependency layers:
name: Build containers
on:
pull_request:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write
strategy:
matrix:
include:
- service: api
context: services/api
- service: worker
context: services/worker
steps:
- uses: actions/checkout@v4
- uses: depot/setup-action@v1
- name: Build ${{ matrix.service }}
uses: depot/build-push-action@v1
with:
project: ${{ vars.DEPOT_PROJECT_ID }}
context: ${{ matrix.context }}Configure a GitHub OIDC trust relationship in the Depot project,
then store its ID in a repository or organization variable named DEPOT_PROJECT_ID. The id-token: write permission
lets each job authenticate without a static Depot token.
The matrix jobs are separate runner machines. They still reach the same remote build service and project cache namespace. Future builds of either service can reuse any valid layers already present in that project.
If one job must populate cache before another job starts, make the consumer job depend on the producer with needs.
That ordering is only necessary when the second job must use layers created earlier in the same workflow. It isn't
required to reuse cache from previous builds.
Runner-local Docker state isn't shared
GitHub-hosted runners are ephemeral. A local docker build writes layers into the Docker daemon on that runner. When
the job finishes, the runner and its Docker state disappear.
Another job gets another runner. Even two jobs in the same workflow can't see each other's local Docker daemon.
Choosing the same runs-on label doesn't make the filesystem or /var/lib/docker persistent.
This leaves two ways to reuse the cache:
- Export the cache from the first runner to remote storage, then download it into the next runner.
- Run the build remotely where the cache already persists.
Depot uses the second model. The GitHub Actions runner sends the build context to a remote BuildKit builder. The builder uses the project's existing cache and writes new layers back to it automatically. The runner can disappear without taking the cache with it.
GitHub cache still moves the cache over the network
The common BuildKit configuration on ephemeral runners uses cache-from: type=gha and
cache-to: type=gha,mode=max. That exports build cache to the GitHub Actions cache service at the end of one build and
imports it at the start of another.
It works, but it turns the cache into data that must be serialized, uploaded, indexed, downloaded, and unpacked. Large Docker caches can spend enough time moving over the network to erase much of the time saved by a cache hit. Cache limits and eviction also mean a warm build can unexpectedly become cold.
With Depot, the cache stays on persistent NVMe storage next to the remote builders. A new job connects to the cache
where it already lives. When using depot/build-push-action, remove cache-from: type=gha and cache-to: type=gha.
Depot caching is automatic, and exporting the same cache to GitHub is unnecessary.
Cache scope follows the Depot project
Depot doesn't create a separate Docker layer cache for every GitHub Actions job or branch. The project is the cache namespace.
That means:
- Jobs in different workflows share cache when they use the same Depot project.
- Builds on trusted branches can reuse valid layers produced on other trusted branches.
- Developers using
depot buildlocally share cache with CI when they use the same project. - Different repositories can intentionally share cache by using the same project.
- Separate Depot projects provide separate cache namespaces when isolation matters more than reuse.
BuildKit still decides whether a layer is valid. Sharing a project doesn't bypass normal Docker cache invalidation. A
changed COPY, RUN, build argument, base image, or Dockerfile instruction can force that layer and everything after
it to rebuild.
For open-source repositories, Depot isolates builds launched from fork pull requests. Those builds run without read or write access to the main project cache, preventing untrusted code from reading or poisoning it.
A shared cache isn't a shared image
The layer cache accelerates another build. It doesn't put the finished image into the Docker daemon of every GitHub Actions runner.
If a later job needs the exact image produced by an earlier job, save it to Depot Registry, push it to another container registry, or explicitly transfer it as an artifact. If the later job only needs to rebuild the same Dockerfile quickly, point it at the same Depot project and let BuildKit reuse the cached layers.
For most GitHub Actions workflows, the practical setup is simple: one Depot project per application or closely related set of images, the same project ID in every build job, and no GitHub cache exporter. That shares the Docker layer cache across CI jobs, workflow runs, and local development without turning the cache into another artifact to move around.