Runner sizes and labels
A GitHub Actions job runs on Anvil's machines when its runs-on names an Anvil size. The repository needs the Anvil app installed and must belong to an invited Anvil account.
jobs:
test:
runs-on: anvil-4cpu
Labels #
| Label | CPUs | Memory | Good for |
|---|---|---|---|
anvil-2cpu |
2 | 6 GB | Lint, type checks, small test suites |
anvil-4cpu |
4 | 12 GB | Most builds and test suites |
anvil-8cpu |
8 | 24 GB | Large builds, Docker images, parallel tests |
anvil |
2 | 6 GB | Same as anvil-2cpu |
anvil-sticky |
Add next to a size for a sticky volume |
Each job gets a fresh container from GitHub's official runner image (ghcr.io/actions/actions-runner), registered for that one job and removed when it ends. It runs as a normal user with sudo inside the container, without access to the host's Docker. CI → Rightsize runners and each job's page tell you when a smaller or bigger size fits better.
Limits #
| Who | Repositories that belong to an invited Anvil account |
| Daily budget | Runner minutes per owner per day, set by the admin (600 by default) |
| Job timeout | 6 hours |
| Price | Anvil doesn't charge for runner minutes. GitHub bills self-hosted runner minutes at its platform rate ($0.002 per minute since March 2026) |
Caching #
Caches live on the same disk as the jobs, so a restore doesn't cross the internet. None of it needs a change to your workflows.
Actions cache #
actions/cache, setup-node, setup-python and the other setup-* caches work unchanged: Anvil runner jobs send cache requests to a cache server on Anvil's machine instead of GitHub's. The same workflow keeps using GitHub's cache on GitHub-hosted runners.
| Rule | |
|---|---|
| Scope | One repository; other repositories and owners never see its entries |
| Branches | A job reads entries from its own branch first, then the default branch. A pull request can restore what main saved, but what it saves never reaches main |
| Overwrites | An entry can't be overwritten; saving an existing key logs a warning. Change the key instead |
| Eviction | Entries unused for 7 days are deleted; past 25 GB per owner (100 GB in total) the least recently used go first |
Docker builds #
Each owner gets a BuildKit daemon of their own on Anvil's machine that keeps its layer cache between jobs. The usual workflow from Docker's docs works unchanged:
jobs:
image:
runs-on: anvil-4cpu
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
docker/setup-buildx-actiongets a builder on your BuildKit instead of starting a BuildKit container. Options that only apply to such a container (driver-opts,buildkitd-flags,buildkitd-config) are ignored. If you pin a Buildxversion:, setdriver: remoteandendpoint: ${{ env.BUILDKIT_HOST }}yourself.docker build .anddocker buildx buildin arun:step use your BuildKit too (a builder namedanvilis preset). Add--pushto get the image out.cache-from/cache-to: type=ghastore the cache in Anvil's Actions cache, with the same rules asactions/cache.
| Cache | Where | Shared by | Kept |
|---|---|---|---|
| BuildKit layer cache | Your BuildKit's volume on your server | All repositories of the owner | Up to 20 GB, oldest layers first |
type=gha |
Anvil's Actions cache | One repository, by branch | 7 days unused, within the 25 GB per owner |
Git checkout #
actions/checkout works unchanged. Anvil keeps a mirror of each repository that runs jobs, so a checkout mostly reads local disk and only fetches what the mirror doesn't have yet.
Sticky volume #
Add anvil-sticky to give a job a persistent directory at $ANVIL_STICKY_DIR (/anvil/sticky), one per repository, kept between jobs. Use it for things slow to rebuild and safe to reuse, such as a package store:
jobs:
test:
runs-on: [anvil-4cpu, anvil-sticky]
steps:
- uses: actions/checkout@v4
- run: pnpm config set store-dir "$ANVIL_STICKY_DIR/pnpm-store"
- run: pnpm install --frozen-lockfile
Only pushes, scheduled and manual runs on the default branch can write to it. Every other job, pull requests included, gets it read-only, with $ANVIL_STICKY_WRITABLE set to 0. Jobs that run at the same time share the volume.
Runners for hosted repositories #
Repositories hosted on Anvil run CI on their own runners, with runs-on: ubuntu-latest. See Find failing, slow and flaky CI.