quickstart →

Magic Cache

Magic Cache swaps the GitHub Actions cache backend for a fast, unlimited S3 cache in your own VPC. Same actions/cache config, nothing leaves your account.

RunsOn Magic Cache transparently switches the GitHub Actions caching backend to a fast, unlimited S3 cache in your own VPC — so you keep using actions/cache (and language-specific cache actions) completely unchanged.

How it works
Your AWS account everything stays in it
your VPC
EC2 runner, one per job
actions/cache your step, unchanged
RunsOn sidecar started by the agent
S3 cache bucket no size cap, your retention
GitHub's cache 10 GB per repository never called
Restores and saves take the same path. The CloudFormation stack's VPC has an S3 gateway endpoint; in your own VPC (Terraform), add one so cache traffic stays off a NAT gateway.
1.8–2.0× faster
4 GB restores against GitHub-hosted, cache benchmark →
unlimited
no 10 gb cap — evicts on your policy
$0 transfer
through the VPC's S3 gateway endpoint

Platform availability#

Cache backendLinuxWindowsFlexFleet
Magic Cache for actions/cacheYesYesYesYes
Docker Buildx type=ghaYesNoYesYes
Docker Buildx type=s3YesNoYesYes
Ephemeral ECR registryYesNoYesYes

How it works#

With the Magic Cache enabled, the RunsOn agent starts a sidecar cache backend on the runner and points the caching actions — including the gha backend for Docker Buildx — at it.

The sidecar transparently forwards every request to an S3 cache bucket in your own VPC and streams hits straight back. Same actions/cache config, nothing leaves your account, and there’s no 10 GB cap.

Because only the storage backend changes, the exact same workflow keeps working when you fall back to GitHub-hosted runners — the cache just points at GitHub’s backend instead.

If the sidecar fails to start (5s timeout), the job falls back to GitHub’s standard cache backend and emits a warning annotation — the job is not failed.

Enabling it#

The Magic Cache is opt-in. Flip it on per job (Flex) or bake it into a runner fleet (Fleet):

Flex

Per job: add the extras=s3-cache job label and the runs-on/action@v2 ↗ step. The workflow author decides.

.github/workflows/ci.yml
runs-on:
- runs-on=${{ github.run_id }}
- runner=2cpu-linux-x64
- extras=s3-cache
steps:
- uses: runs-on/action@v2
Fleet

Per runner: publish cache-enabled runners from Terraform. The platform team decides.

main.tf
runners = {
linux-cache = {
cpu = 4
family = ["c8i"]
extras = ["s3-cache"]
}
}
AspectFlexFleet
Who choosesWorkflow author, per jobPlatform team, in Terraform
Where it livesextras=… label or .github/runs-on.ymlrunners.<name>.extras in Terraform
Magic Cache (S3)✓✓
Docker BuildKit S3✓✓
Ephemeral registry✓✓

The detailed guides below have copy-paste examples for both models.

Retention#

S3 Lifecycle calculates expiration by adding a fixed lifecycle window (default 10 days) and rounding up to the next midnight UTC. Restoring an object does not extend that window. For Fleet and Flex, the Terraform module provisions the bucket and exposes the window:

main.tf
cache_expiration_days = 10

Use a longer window for slow-changing dependency caches, and a shorter one for high-churn branches or very large artifacts.

Scope and isolation#

By default, Magic Cache keeps the stack-wide access model used by earlier v3 releases. Cache isolation scopes the Magic Cache protocol, but it is not a trust boundary for untrusted job code. Run public or otherwise untrusted repositories in a separate RunsOn stack from trusted workloads.

Isolate GitHub Actions caches#

We recommend cache isolation on every stack. v3.2 adds opt-in, brokered credentials for Magic Cache. They scope actions/cache, language setup actions, and the Buildx type=gha backend to the current repository, and keep GitHub’s restore rules. The scopes come from the job’s GitHub-signed runtime token: a job reads and writes its own ref, and can read the default branch (and, for a pull request, its base branch). So branches and pull requests still fall back to default-branch entries.

DeploymentSetting
Flex CloudFormationEnableCacheIsolation: "true"
Flex Terraform / OpenTofuenable_cache_isolation = true
Fleet Terraform / OpenTofuenable_cache_isolation = true

Isolation stores Magic Cache data under scoped-cache/* instead of cache/*. Expect one cold cache per repository and branch when you enable it. That comes from the one-time move to the new prefix, not from a change to the fallbacks above. Existing objects keep their normal retention and expire without migration.

Fleet capacity-backed runners also get one cold classic Magic Cache after a v3.2 upgrade, even if isolation remains off. v3.2 corrects their old empty repository prefix and repopulates the matching cache/v1/<owner>/<repo>/... namespace automatically.

The separate v3.2 upgrade guide covers a safe rollout and the sticky-disk isolation that removes legacy snapshot permissions.

Detailed guides#

Other ways to cache#

The Magic Cache covers actions/cache and Docker layers. For caches tied to the runner’s disk rather than an S3 backend, see the runner capabilities: