Skip to documentation

Route jobs

Configure workflows

Configure a local-infrastructure route and GitHub-hosted fallback in your existing workflow.

Your workflow’s runs-on selects a job’s execution route. This guide configures local infrastructure with GitHub-hosted fallback while keeping your build and test steps. Other jobs in the workflow can use different providers.

For a direct Cloudflare route, see Cloudflare runners. To compare providers and change a job’s default through Mirage-generated pull requests, use Compare CI providers.

Change runs-on

Start with an existing build or test job. Keep its steps and replace its GitHub-hosted runner label with the expression for the same operating system and architecture. For a job that currently uses ubuntu-24.04:

Linux x64 · replace the job’s runs-on
runs-on: ${{ github.run_attempt == '1' && 'mirage-linux-x64' || 'ubuntu-24.04' }}

On the first attempt, GitHub sends the job to Mirage. If Mirage needs to use fallback, it requests a second attempt of the same workflow; the expression selects GitHub’s runner for that attempt. There is no extra setup job or Mirage action to add.

Runner labels

Job environmentMirage labelGitHub fallback
Linux x64mirage-linux-x64ubuntu-24.04
Linux arm64mirage-linux-arm64ubuntu-24.04-arm
macOS arm64mirage-macos-arm64macos-26
Linux arm64
runs-on: ${{ github.run_attempt == '1' && 'mirage-linux-arm64' || 'ubuntu-24.04-arm' }}
macOS arm64
runs-on: ${{ github.run_attempt == '1' && 'mirage-macos-arm64' || 'macos-26' }}

Choose the architecture your tools support. Linux x64 on an Apple-silicon Mac needs Rosetta; native macOS needs an enabled and ready macOS runner profile. The machine’s details page lists each profile and any setup still required.

Tools, secrets, and caches

Mirage’s runner image has its own toolchain; it does not include everything from GitHub’s hosted images. Use setup actions or installation steps to select the versions your project needs, and install your dependencies before building or testing.

Jobs start in fresh environments. Use checkout to fetch your code, GitHub Actions artifacts to pass files between jobs, and cache actions for dependencies. Keep secrets in GitHub Actions and reference them as you already do. Tools and files installed in your Mac’s user account are not automatically available inside a job.

Linux runners on Macs support Docker commands, service containers, job containers, and Docker container actions. Cloudflare runners have different limits and do not support those Docker features.

Parallel jobs

Each running job needs a slot. A Mac starts with one shared slot across its profiles and accounts. If you submit several jobs at once, insufficient capacity can trigger fallback. Add machines, raise concurrency when the host has enough resources, or use needs to run jobs in sequence:

Two sequential jobs · jobs section
jobs:
  first:
    runs-on: ${{ github.run_attempt == '1' && 'mirage-linux-arm64' || 'ubuntu-24.04-arm' }}
    steps:
      - run: echo "First job"

  second:
    needs: first
    runs-on: ${{ github.run_attempt == '1' && 'mirage-linux-arm64' || 'ubuntu-24.04-arm' }}
    steps:
      - run: echo "Second job"

Each job still gets a fresh environment. A dependency controls execution order; it does not share a working directory.

How fallback works

If your machines cannot take the job or a runner has an infrastructure failure, Mirage can cancel attempt 1 and request one rerun of the whole workflow. Attempt 2 uses your configured fallback. GitHub keeps both attempts under the same run ID.

  • A failed test or build is a CI result and does not trigger an infrastructure retry.
  • A manual rerun also increments the attempt, so the expression selects GitHub-hosted runners.
  • Fallback needs Mirage and GitHub to be reachable. A Mirage outage can leave a job queued.
  • GitHub-hosted jobs use your GitHub Actions allowance or billing.

Run only on your machines

If you want every attempt to target Mirage, use a literal label:

No GitHub-hosted fallback
runs-on: mirage-linux-arm64

This label cannot switch to a GitHub-hosted runner on a rerun. If no eligible machine is available, the job cannot complete there. Use the attempt expression above when you want automatic hosted fallback.