DocsRunner sizes and labels
Docs/Reference

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.

yaml
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:

yaml
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-action gets 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 Buildx version:, set driver: remote and endpoint: ${{ env.BUILDKIT_HOST }} yourself.
  • docker build . and docker buildx build in a run: step use your BuildKit too (a builder named anvil is preset). Add --push to get the image out.
  • cache-from/cache-to: type=gha store the cache in Anvil's Actions cache, with the same rules as actions/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:

yaml
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.