Caching for Docker builds
Cache Docker layers on RunsOn runners with the Magic Cache (type=gha), the S3 BuildKit backend (type=s3), or the ephemeral ECR registry (type=registry).
Availability#
All Docker caching backends on this page work on Linux runners only. Magic Cache and direct S3 caching work with Flex and Fleet. The ephemeral ECR registry works with Flex only.
RunsOn gives you three ways to cache Docker layers, all backed by storage inside your own AWS account. They trade off simplicity vs. speed — try each on your workload and pick the fastest:
| Backend | Buildx type | Notes |
|---|---|---|
| Magic Cache | type=gha | Transparent — reuses the standard GitHub cache backend, swapped to S3 behind the scenes. Simplest. |
| S3 BuildKit | type=s3 | Points Buildx directly at the RunsOn cache bucket. Durable and simple. |
| Ephemeral registry | type=registry | Registry-native (ECR). Often the fastest, and lets you share full images across jobs. |
Magic Cache (type=gha)#
If you already enable the Magic Cache with extras=s3-cache, your type=gha Buildx exporter is transparently redirected to S3 — no extra configuration:
jobs: docker: runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache steps: - uses: runs-on/action@v2 - uses: actions/checkout@v7 - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v4 with: context: "." push: false tags: test cache-to: type=gha,mode=max cache-from: type=ghaIsolation#
type=gha uses the Magic Cache protocol. When you enable Magic Cache
isolation, it receives the same repository-and-branch boundary as
actions/cache. Its first restore in each new scope is cold. See Magic Cache
scope for the rollout
settings.
S3 BuildKit backend (type=s3)#
The same S3 cache bucket can be used directly with the type=s3 Buildx cache backend. In our tests it is faster than type=gha and slightly slower than the ephemeral registry.
Flex#
Run on a cache-enabled runner and point Buildx at the RunsOn cache bucket:
jobs: docker: runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache steps: - uses: runs-on/action@v2 - uses: actions/checkout@v7 - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v4 with: context: "." push: true tags: <your-tag> cache-from: type=s3,blobs_prefix=${{ env.RUNS_ON_S3_CACHE_REPO_PREFIX }}/buildkit/,manifests_prefix=${{ env.RUNS_ON_S3_CACHE_REPO_PREFIX }}/buildkit/,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }} cache-to: type=s3,blobs_prefix=${{ env.RUNS_ON_S3_CACHE_REPO_PREFIX }}/buildkit/,manifests_prefix=${{ env.RUNS_ON_S3_CACHE_REPO_PREFIX }}/buildkit/,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }},mode=maxFleet#
For Fleet, enable S3 cache behavior in the runner fleet and use the same Buildx cache settings in workflow YAML:
runners = { linux-docker = { cpu = 8 ram = 16 family = ["c8i"] image = "ubuntu24-full-x64" extras = ["s3-cache"] }}Authentication is handled automatically: as long as the workflow runs on a
RunsOn runner, Buildx uses the runner’s IAM role to access the S3 bucket. This
is a direct S3 client, so it keeps stack-shared access to cache/* even when
Magic Cache isolation is enabled. Use it only for layers that can be shared by
that stack, or use separate stacks and environments
to create a stronger boundary.
Ephemeral registry (type=registry)#
Since v2.8.2, RunsOn can automatically create an ephemeral registry (an ECR repository in your AWS account) to temporarily store images and layers shared across build jobs — no external registry to configure. In our tests, type=registry was slightly faster than both type=s3 and type=gha.
Flex#
Enable the registry login helper with the ecr-cache extra, then use RUNS_ON_ECR_CACHE as your registry target:
jobs: ecr-cache: runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=ecr-cache steps: - uses: runs-on/action@v2 - uses: actions/checkout@v7 - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v4 env: TAG: ${{ env.RUNS_ON_ECR_CACHE }}:my-app-latest with: context: . push: true tags: ${{ env.TAG }} cache-from: type=registry,ref=${{ env.TAG }} cache-to: type=registry,ref=${{ env.TAG }},mode=maxFleet#
Enable the stack-scoped registry, then add ecr-cache to the runner definition
used by a Linux fleet:
enable_ecr = true
runners = { linux-cache = { family = ["m7i.large"] image = "ubuntu24-full-x64" extras = ["ecr-cache"] }}Publish a fleet backed by that runner definition. Jobs targeting the fleet get
the same RUNS_ON_ECR_CACHE environment variable and automatic Docker login as
Flex jobs.
Use cases#
The ephemeral registry can be used to:
- Push and pull images that need to be temporarily shared across workflow jobs. For instance, build an image in one job (e.g. for integration tests) and use it in one or many dependent jobs.
- Cache Docker layers across workflow jobs.
Accessing the ephemeral registry#
Runners automatically get push/pull access to this registry through the EC2 instance profile. The RUNS_ON_ECR_CACHE environment variable contains the full registry URL (e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com/runs-on-ab12cd34-ephemeral-registry). The repository name is auto-generated per stack, so always reference ${{ env.RUNS_ON_ECR_CACHE }} rather than hardcoding a path. When you include ecr-cache in the extras label, the RunsOn agent logs into the registry before your job starts.
Retention, pricing, and considerations#
The ephemeral registry makes tagged and untagged images eligible for expiration 10 days after they are pushed. This policy is the same for CloudFormation and Terraform / OpenTofu installs. AWS can apply the lifecycle policy up to 24 hours after an image becomes eligible.
Pricing example#
The following uses US East (N. Virginia) Amazon ECR private-repository pricing ↗. It excludes EC2, VPC interface endpoints, NAT Gateway, cross-Region or Internet transfer, taxes, and free-tier credits, and optional enhanced scanning. It assumes 20 GB of retained ECR storage and AWS’s 30-day-month proration.
| Retained ECR storage | ECR storage | Same-Region ECR ↔ EC2 transfer | Total |
|---|---|---|---|
| 20 GB retained for 10 days | $0.67 | $0 | $0.67 |
| 20 GB retained for 30 days | $2.00 | $0 | $2.00 |
ECR charges $0.10 per GB-month for private-repository storage. Same-Region EC2 runners can push and pull the cache without an ECR data-transfer charge. The 10-day case can reach about $0.73 when AWS applies its lifecycle policy up to 24 hours later.
The registry is shared across all runners launched by the same RunsOn stack, so only store images and layers that can be shared; create multiple environments if you need better isolation.
On the built-in v3 CloudFormation path, the embedded stack creates the free S3 gateway VPC endpoint but not EC2 or ECR interface VPC endpoints. If your runners use private networking and need private ECR access, add the relevant interface endpoints through Terraform / OpenTofu or your own networking stack.
Limitations#
- All three Docker caching backends are only available on Linux runners.
- For a native, persistent BuildKit cache on the runner disk, see Sticky disks.