Magic Cache for GitHub Actions
How to enable RunsOn Magic Cache for actions/cache and language setup actions — unlimited, S3-backed caching with no per-step changes.
The Magic Cache accelerates actions/cache and every language-specific cache action (actions/setup-node, ruby/setup-ruby, …) by swapping their storage backend to a fast, unlimited S3 bucket in your VPC — with no changes to your cache steps.
See how the Magic Cache works for the request path; this page is the practical how-to for GitHub Actions caches.
Availability#
Magic Cache for GitHub Actions works on Linux and Windows runners, with both Flex and Fleet.
Usage#
Flex#
Enable the S3-backed cache sidecar from the job label, then keep using the normal GitHub cache action:
jobs: build: runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache steps: - uses: runs-on/action@v2 - uses: actions/cache@v5 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}Fleet#
For Fleet, publish runners with Magic Cache enabled from Terraform:
runners = { linux-cache = { cpu = 4 ram = 8 family = ["c8i"] image = "ubuntu24-full-x64" extras = ["s3-cache"] }}Workflow jobs can then keep using actions/cache on that runner fleet.
How to use#
Using the Magic Cache is simple:
- Set an additional job label:
extras=s3-cache. - Add the
runs-on/action@v2↗ action to your job. - Use the normal caching actions as before.
Note that when running on official GitHub Actions runners, the runs-on/action@v2 action is a no-op, so it’s fine to keep in your workflows even if you mix official and RunsOn runners.
It also transparently accelerates the type=gha Buildx exporter when building Docker images — see Caching for Docker.
Accelerate actions/cache#
As an example, the workflow below compares the speed of the Magic Cache vs official runners for multiple cache sizes by generating a random file, saving it to the cache, and then restoring it. It uses the official actions/cache action, but it should also work with any language-specific caching action (e.g. actions/setup-node, ruby/setup-ruby, etc.).
jobs: test-magic-cache-speeds: strategy: fail-fast: false matrix: runner: - runs-on=${{github.run_id}}/runner=2cpu-linux-x64/extras=s3-cache - ubuntu-latest blocks: - 4096 # 4GB - 2048 # 2GB - 512 # 512MB - 64 # 64MB runs-on: ${{ matrix.runner }} env: FILENAME: random-file steps: - uses: runs-on/action@v2 - name: Generate file run: | echo "Generating ${{ matrix.blocks }}MiB random file..." dd if=/dev/urandom of=${{ env.FILENAME }} bs=1M count=${{ matrix.blocks }} ls -lh ${{ env.FILENAME }} - name: Save to cache (actions/cache) uses: actions/cache/save@v4 with: path: ${{ env.FILENAME }} key: github-${{github.run_id}}-actions-cache-${{strategy.job-index}}-${{ matrix.blocks }}MiB-${{ env.FILENAME }} - name: Restore from cache (actions/cache) uses: actions/cache/restore@v4 with: path: ${{ env.FILENAME }} key: github-${{github.run_id}}-actions-cache-${{strategy.job-index}}-${{ matrix.blocks }}MiB-${{ env.FILENAME }} - name: Restore from cache (actions/cache, restoreKeys) uses: actions/cache/restore@v4 with: path: ${{ env.FILENAME }} key: github-${{github.run_id}}-actions-cache-${{strategy.job-index}}-unknown restore-keys: | github-${{github.run_id}}-actions-cache-${{strategy.job-index}}-${{ matrix.blocks }}MiB-FAQ#
Speed#
Speed depends on file size and instance type, but the larger the files and the larger the instance type, the faster (up to 5x compared to official runners) it is for saving and restoring.
Size#
The cache has no RunsOn size cap. S3 Lifecycle calculates expiration by adding the fixed retention window configured on your RunsOn stack (default: 10 days) and rounding up to the next midnight UTC. Cache hits do not extend that window.
Pricing example#
The following compares 50 GB of cache data in US East (N. Virginia). Magic Cache uses its default 10-day S3 Lifecycle setting, which results in 10–11 days of billable storage because S3 rounds expiration up to the next midnight UTC ↗. It uses Amazon S3 Standard pricing ↗ and GitHub Actions cache-storage pricing ↗. This is a storage-only comparison. It excludes EC2, S3 API and applicable KMS request charges, NAT Gateway, VPC interface endpoints, cross-Region or Internet transfer, taxes, and free-tier credits. It uses 30-day-month proration.
| Backend | Can retain 50 GB? | Storage cost with a 10-day setting |
|---|---|---|
| Magic Cache (S3 Standard) | Yes | $0.38–$0.42 |
| GitHub Actions cache, default | No — 10 GB per repository | — |
| GitHub Actions cache, capacity raised to 50 GB | Yes | $0.93 |
Magic Cache storage costs 50 GB × $0.023/GB-month × 10–11/30 = $0.38–$0.42.
GitHub includes 10 GB per repository, so the 50 GB option bills the remaining
40 GB at
$0.07/GB-month × 40 GB × 10/30 = $0.93. A GitHub owner must raise the repository’s
cache capacity ↗
to retain 50 GB; otherwise, its default cache limit evicts older data.
S3 request and applicable KMS charges still apply. Their total depends on the number and size of cache saves and restores. In the same Region, transfer between S3 and EC2 is free. NAT Gateway, VPC interface endpoints, and cross-Region transfer can add separate network charges.
Magic Cache objects follow that fixed expiry schedule even if they are restored. GitHub removes caches unused for seven days by default ↗, so keep GitHub entries active or configure its retention for a like-for-like comparison.
Scope and isolation#
Magic Cache isolation is opt-in in v3.2. Enable it with
EnableCacheIsolation: "true" for Flex CloudFormation or
enable_cache_isolation = true for either Terraform module. The cache broker
then gives actions/cache and language setup actions temporary credentials
limited to the current repository and branch.
Enabling isolation moves Magic Cache data from cache/* to scoped-cache/*.
The first run for each repository and branch is cold; the older objects expire
on the normal retention schedule.
Limitations#
- Magic Cache is opt-in: add
extras=s3-cacheandruns-on/action@v2to Flex jobs, or publish cache-enabled Fleet runners.
Common issues#
actions/upload-artifact compatibility#
If you have enabled the s3-cache extra and you are using actions/upload-artifact@v4, you must also include the runs-on/action@v2 action in your jobs. Otherwise you might see an error like:
Attempt 1 of 5 failed with error: Unexpected token 'O', "Original A"... is not valid JSON. Retrying request in 3000 ms...Alternate: the runs-on/cache action#
The runs-on/cache ↗ action is a more general way to send caches to an S3 bucket — it works with or without RunsOn, including from GitHub-hosted or other providers’ runners. It’s a drop-in replacement for actions/cache that changes only the storage backend (300–500 MB/s throughput, no 10 GB limit, automatic fallback to GitHub’s cache when run outside RunsOn).
See the runs-on/cache repository ↗ for setup (configuring AWS credentials, pointing at your bucket, and the unset-current-credentials option when credentials from a previous step take precedence).