What Are Snapshot Runners?
A snapshot runner boots a GitHub Actions job from a saved image of an earlier run's VM instead of a clean base image. What persists, what resets, what it costs.
Last verified:
A snapshot runner is a GitHub Actions runner that boots from a saved image of a previous run's virtual machine instead of from a clean base image, so the packages, dependency directories, container images, and build outputs an earlier run left on disk are already present when the first step executes. WarpBuild implements snapshot runners on its Cloud Ubuntu runners: you append snapshot.enabled=true or snapshot.key=<alias> to the runs-on label, capture the machine with the WarpBuilds/snapshot-save action, and later jobs that request the same alias start from that captured disk.
Answer
Every job on an ephemeral runner starts from an identical base image. That is what makes GitHub Actions reproducible, and it is also why the same forty minutes of apt-get install, npm ci, docker pull, and cold compilation get repeated on every pull request in a busy repository. A snapshot runner changes the starting point of that job without changing the isolation model.
Three properties define the mechanism:
- The unit is the whole runner disk. A snapshot is taken at whatever point in the workflow you call the save action, and it captures the machine as it stood at that moment, including files no cache action was ever configured to track.
- A snapshot is addressed by an alias you choose. The alias is a string such as
web-app-main. A job asks for it by puttingsnapshot.key=web-app-mainin itsruns-onlabel. - The job stays ephemeral. WarpBuild allocates a fresh virtual machine per job and destroys it when the workflow completes. The saved image is what survives, and it survives for 15 days.
That third property is what separates a snapshot runner from a persistent self-hosted runner. A persistent runner keeps one machine registered and hands it job after job, accumulating state that nobody chose and that no branch can reset. A snapshot runner builds a new machine for every job from a disk image you named and can rebuild on demand.
The distinction from caching is just as concrete. A cache action restores named paths into a clean machine. Restoring 3 GB of node_modules still leaves the apt packages uninstalled, the Docker daemon's local image store empty, the Rust target directory partial, and every compiled artifact outside the cached paths missing. A snapshot carries the disk, so whatever a previous run installed anywhere on that disk is present at step one. The WarpBuild snapshot runners documentation is the reference for the exact behavior, and the persistent caches guide covers the cases where a cache is the better tool.
Platform scope is the first thing to check before designing around this. Snapshot runners apply to the Ubuntu part of that catalog, described in the cloud runners documentation.
Detail
What carries over and what resets
A snapshot holds the runner VM's disk at the instant snapshot-save ran. Installed system packages, cloned repositories, dependency directories, container images pulled into the local Docker store, compiler caches, and generated build outputs all come back with the machine.
Two behaviors need planning around.
/tmp does not persist. The directory is cleaned on reboots, and booting from a snapshot is a boot, so anything a job wrote to /tmp before the save is gone on restore. Build tooling that stages artifacts in /tmp needs a different path if you want those artifacts in the snapshot.
Secrets on disk do persist. Credentials written by a login step, an SSH key, an AWS profile, or a token file left in a working directory are part of the disk and therefore part of the image. This is the reason the documentation puts a cleanup step immediately before every save, and the reason the security section below matters more than the cost section.
One signal comes back with the machine: a runner created from a snapshot has the environment variable WARPBUILD_SNAPSHOT_KEY set to the alias it booted from. That variable is how a workflow knows whether it is on a warm machine and can skip the setup steps that a warm machine no longer needs.
The two snapshot labels
Snapshot behavior is requested through dynamic labels appended to the runner label with a semicolon. There are two, and they do different jobs:
snapshot.enabled=trueturns the feature on and always boots from the base image. Use it on the workflow that produces the snapshot, so that the image you capture is built from a known clean starting point.snapshot.key=<alias>turns the feature on and boots from the existing snapshot for that alias when one exists. When no snapshot exists yet for the alias, the runner boots from the base image and the job proceeds normally.
Both forms are written inline in runs-on, for example warp-ubuntu-latest-x64-4x;snapshot.key=web-app-main. If either label is applied to a runner type that does not support snapshots, the label is silently ignored and the job runs without snapshot behavior. There is no error and no warning in the job log, so a typo in the runner label is indistinguishable from a working configuration until you check whether WARPBUILD_SNAPSHOT_KEY was set.
Capturing a snapshot
The capture step is the WarpBuilds/snapshot-save action, published at github.com/WarpBuilds/snapshot-save. Place it at the point in the workflow where the disk holds the state you want the next run to start from, which is usually after dependencies are installed and the build has warmed the compiler caches, and always after credentials have been removed.
| Input | Required | Default | Purpose |
|---|---|---|---|
alias | Yes | none | Unique name for the snapshot, used later in snapshot.key |
fail-on-error | No | true | Fail the job when snapshot creation errors |
wait-timeout-minutes | No | 30 | Maximum minutes to wait for the snapshot to be created |
Setting fail-on-error: false is the right choice when the snapshot is an optimization and a failed capture should leave the pipeline green. Raising wait-timeout-minutes above the default matters for large disks, where the capture takes longer than half an hour.
Runners that accept snapshot labels
Snapshot runners are supported only on WarpBuild Cloud Ubuntu runners. The table below lists the Ubuntu 24.04 labels and their published rates, taken from the cloud runners documentation and the pricing page, checked on 2026-08-13.
| Runner label | OS | vCPU | RAM | Storage | Price per minute |
|---|---|---|---|---|---|
warp-ubuntu-latest-x64-2x | Ubuntu 24.04 | 2 | 8 GB | 150GB SSD | $0.004 |
warp-ubuntu-latest-x64-4x | Ubuntu 24.04 | 4 | 16 GB | 150GB SSD | $0.008 |
warp-ubuntu-latest-x64-8x | Ubuntu 24.04 | 8 | 32 GB | 150GB SSD | $0.016 |
warp-ubuntu-latest-x64-16x | Ubuntu 24.04 | 16 | 64 GB | 150GB SSD | $0.032 |
warp-ubuntu-latest-x64-32x | Ubuntu 24.04 | 32 | 128 GB | 150GB SSD | $0.064 |
warp-ubuntu-latest-arm64-2x | Ubuntu 24.04 | 2 | 8 GB | 150GB SSD | $0.003 |
warp-ubuntu-latest-arm64-4x | Ubuntu 24.04 | 4 | 16 GB | 150GB SSD | $0.006 |
warp-ubuntu-latest-arm64-8x | Ubuntu 24.04 | 8 | 32 GB | 150GB SSD | $0.012 |
warp-ubuntu-latest-arm64-16x | Ubuntu 24.04 | 16 | 64 GB | 150GB SSD | $0.024 |
warp-ubuntu-latest-arm64-32x | Ubuntu 24.04 | 32 | 128 GB | 150GB SSD | $0.048 |
The Ubuntu 26.04 labels carry the same sizes and rates for both x64 and ARM64, under warp-ubuntu-2604-*. Ubuntu 22.04 carries the same x64 sizes and rates under warp-ubuntu-2204-*; WarpBuild deprecated the Ubuntu 22.04 ARM64 images on March 31, 2025, so the 22.04 label is x64 only. Each latest label also has a pinned alias, so warp-ubuntu-latest-x64-4x and warp-ubuntu-2404-x64-4x route to the same machine shape. A snapshot boots on the runner shape you request in runs-on, so keep the size stable across the workflow that saves the snapshot and the workflows that restore it.
A workflow that saves on main and restores on pull requests
This is the shape most teams want. The default branch always boots clean and republishes the snapshot, so the image never drifts. Pull request runs boot from the published image and skip the setup work it already contains.
name: build
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: >-
${{ github.ref == 'refs/heads/main'
&& 'warp-ubuntu-latest-x64-4x;snapshot.enabled=true'
|| 'warp-ubuntu-latest-x64-4x;snapshot.key=web-app-main' }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install system packages
run: |
if [ -z "$WARPBUILD_SNAPSHOT_KEY" ]; then
sudo apt-get update
sudo apt-get install -y libvips-dev protobuf-compiler
else
echo "booted from snapshot $WARPBUILD_SNAPSHOT_KEY"
fi
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Test
run: npm test
- name: Cleanup credentials
if: github.ref == 'refs/heads/main'
run: |
rm -rf $HOME/.ssh $HOME/.aws
git clean -ffdx
- name: Save snapshot
if: github.ref == 'refs/heads/main'
uses: WarpBuilds/snapshot-save@v1
with:
alias: "web-app-main"
fail-on-error: true
wait-timeout-minutes: 60The npm ci step is left unguarded on purpose. It reconciles the lockfile against the node_modules tree that came out of the snapshot, which is fast when the tree is already correct and correct when the lockfile moved on since the image was captured. Guard the steps whose work is expensive and idempotent, such as system package installation and toolchain downloads, and leave the steps that enforce correctness running every time.
The second shape is a single alias that updates itself on every run, with no separate producing branch:
jobs:
integration:
runs-on: warp-ubuntu-latest-x64-8x;snapshot.key=integration-fixtures
steps:
- uses: actions/checkout@v5
- name: Seed fixtures
run: ./scripts/seed-fixtures.sh
- name: Integration tests
run: ./scripts/integration.sh
- name: Cleanup credentials
run: |
rm -rf $HOME/.ssh $HOME/.aws
git clean -ffdx
- uses: WarpBuilds/snapshot-save@v1
with:
alias: "integration-fixtures"
fail-on-error: falseEach run boots from the previous run's disk and writes a new one, so state compounds run over run. That is useful for fixture data and large downloaded corpora. It also means a bad run can poison the alias, and the fix is to change the alias string, which sends the next job back to the base image.
Cleanup and security
Two exposure paths deserve explicit handling before a snapshot alias goes into a workflow file.
On public repositories, a pull request workflow can name any alias in runs-on. A contributor who reads your workflow file knows the alias, so any credential left in a snapshot is reachable by anyone who can open a pull request. Cloud credentials, registry tokens, and signing keys belong outside snapshots on public repositories.
On private repositories, WarpBuild provisions runners at the organization level, and GitHub may allocate a runner intended for a snapshot job to a different job in the same organization. A snapshot that carries secrets can therefore surface them to other users in the organization.
The mitigation is the same in both cases: remove credentials before the save step runs.
rm -rf $HOME/.ssh $HOME/.aws
git clean -ffdxgit clean -ffdx removes untracked files from the repository working tree, including directories and files ignored by .gitignore. It is the blunt version of the cleanup and it will also delete build output you may have wanted in the snapshot, so on repositories where the build directory is gitignored, replace it with targeted deletions of the paths that hold secrets.
The 15-day lifetime
Snapshots are temporary and are deleted after 15 days. An alias that no workflow refreshes stops resolving after that window, and the next job requesting it boots from the base image and runs normally. Nothing fails, and the only visible symptom is a job that takes its cold-boot duration again. A workflow that saves on every merge to the default branch refreshes the alias continuously and never reaches the expiry, which is another argument for the save-on-main pattern above.
Boot behavior is the other timing fact worth knowing. Booting a snapshot runner can be slower than booting a default runner and takes 45 to 60 seconds, per the snapshot runners documentation. Snapshots pay for themselves when the state they carry costs more than a minute to rebuild, and the arithmetic below turns that into a threshold you can check against your own job durations.
What snapshot runners cost
Two line items sit on top of the runner's per-minute rate, both from the pricing page, checked on 2026-08-13:
- Snapshot restore: $0.04 per job.
- Snapshot storage: $0.025 per hour per snapshot.
The break-even is straightforward. Let r be the runner's per-minute rate and S the setup minutes the snapshot lets a job skip. Restoring costs one extra boot minute plus the $0.04 restore fee, so a snapshot pays off when S > 1 + 0.04 / r.
| Runner label | Price per minute | Break-even setup time skipped |
|---|---|---|
warp-ubuntu-latest-x64-2x | $0.004 | 11 minutes |
warp-ubuntu-latest-x64-4x | $0.008 | 6 minutes |
warp-ubuntu-latest-x64-8x | $0.016 | 3.5 minutes |
warp-ubuntu-latest-x64-16x | $0.032 | 2.25 minutes |
warp-ubuntu-latest-x64-32x | $0.064 | 1.6 minutes |
The threshold falls as the runner gets larger, because the fixed $0.04 restore fee buys fewer minutes at a higher rate. On a 2 vCPU runner, a snapshot needs to remove more than 11 minutes of setup work to be worth its fee. On a 16 vCPU runner, 2.25 minutes is enough.
Here is a full month for a repository with a real workload. The assumptions are stated so you can substitute your own: 40 workflow runs per weekday across 22 weekdays, which is 880 runs per month; a job that takes 12 minutes on a clean boot, of which 7 minutes is environment setup; and one live snapshot alias refreshed on every merge, held for the whole month at 720 hours. With the snapshot, setup drops to zero and boot adds a minute, so the job takes 6 minutes.
The GitHub column prices the same 12-minute job on the 16-core Linux larger runner at $0.042 per minute, from the GitHub Actions billing reference, checked on 2026-08-13. GitHub-hosted runners have no snapshot equivalent, so that column carries no snapshot line items.
| Line | GitHub 16-core Linux larger runner | warp-ubuntu-latest-x64-16x, no snapshot | warp-ubuntu-latest-x64-16x, snapshot |
|---|---|---|---|
| Rate per minute | $0.042 | $0.032 | $0.032 |
| Minutes per job | 12 | 12 | 6 |
| Runner minutes per month | 10,560 | 10,560 | 5,280 |
| Runner cost per month | $443.52 | $337.92 | $168.96 |
| Snapshot restore fees | none | $0 | $35.20 |
| Snapshot storage | none | $0 | $18.00 |
| Total per month | $443.52 | $337.92 | $222.16 |
The gap between the first and third columns is $221.36 per month for this workload. Per-size arithmetic for every label is on the pricing page. Two of the assumptions carry most of the weight. Setup minutes is the sensitive one: at 3 minutes of setup instead of 7, the snapshot column loses its advantage over the plain runner column, which is exactly what the break-even table predicts for a 16 vCPU runner. Storage is the other: $18.00 assumes a single alias alive continuously. Ten aliases held the same way is $180.00 per month, so per-branch snapshot aliases are worth counting before they are worth adopting.
Two structural notes belong with any of these numbers. The snapshot runners hub covers the rollout patterns, and the cache size limit guide covers the cases where a cache is the cheaper answer.
Related Questions
Do snapshot runners work on Windows, macOS, or BYOC runners?
No. Snapshot runners are supported only on WarpBuild Cloud Ubuntu runners. BYOC runners, Windows runners, and macOS runners are not supported, and snapshot labels on those runner types are silently ignored, so the job runs normally with no snapshot behavior and no error. The snapshot runners hub lists the Ubuntu labels that accept the feature.
How long does a snapshot last?
Snapshots are temporary and are deleted after 15 days. An alias that stops being refreshed therefore stops resolving after 15 days, and the next job that requests it boots from the base image instead of failing. The snapshot runners documentation is the reference for the lifetime and the save action inputs.
Are snapshot runners the same thing as a cache?
No. A cache action restores named paths into a clean machine, so apt packages, Docker images in the local daemon store, and files outside the cached paths are still missing. A snapshot restores the whole runner disk as it stood when the save action ran. The persistent caches guide walks through which of the two fits a given workflow.
What do snapshot runners cost?
Snapshot restore is billed at $0.04 per job and snapshot storage at $0.025 per hour per snapshot, on top of the per-minute rate of the Ubuntu runner you selected. The pricing page carries the per-minute rate for every runner label, and GitHub Actions runner defines the underlying terms.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.