Migrate from ARC to WarpBuild

Move GitHub Actions jobs off Actions Runner Controller. Map scale-set values to WarpBuild runners, change runs-on, run both in parallel, then decommission.

Last updated:

Moving GitHub Actions jobs from Actions Runner Controller to WarpBuild is two pieces of work with very different sizes. The workflow change is one line per job. The cluster change is a decommissioning project you can take at your own pace, because ARC and WarpBuild runners coexist without conflict.

This page maps the gha-runner-scale-set configuration surface to WarpBuild runner configuration, shows the runs-on diff and a full workflow, covers caching, the parallel run, rollback, and the teardown order for the cluster deployment.

Prerequisites

  • A WarpBuild account and the GitHub App installed. Sign up at app.warpbuild.com, then install the WarpBuild GitHub bot from the dashboard and grant access to the repositories you plan to move. The bot cannot be installed from the GitHub Marketplace directly (quick start).
  • Organization owner access on GitHub, the same level ARC needed. WarpBuild registers its runners in your organization's Default runner group, id 1. If your workflows run in public repositories, check "Allow public repositories" on that group (public repos). If your organization uses a non-default runner group, confirm the group selected in WarpBuild has access to the repository (common issues).
  • Your ARC values.yaml files. You need the scale-set values for the mapping table below, and the controller values if you customized flags or the image repository.
  • A cloud account, only if you want BYOC. For runners inside your own AWS, GCP, or Azure account, connect the cloud account, create a stack that pins region, VPC, and object storage, and define a custom runner (BYOC docs). Skip this if WarpBuild-hosted runners are acceptable.
  • GitHub Enterprise Server or GitHub Enterprise Cloud with data residency needs the enterprise app flow instead of the default install, which is available on the Enterprise plan (GHE setup).

TODO(verification): no published figure exists in the repo for expected time to first green build. Add one once a measured number is available.

Map Your Scale Set Configuration

Read the left column out of your values.yaml. Chart keys and their documented meanings come from the gha-runner-scale-set chart and the gha-runner-scale-set-controller chart at chart version 0.14.2, read on 2026-08-13.

ARC valueWhat it does on ARCWarpBuild equivalent
githubConfigUrlRepository, organization, or enterprise scope for the runnersThe repositories you grant the WarpBuild GitHub App at install time. Enterprise estates use the GHE app flow.
githubConfigSecretPAT or GitHub App credentials stored as a Kubernetes secret in the scale-set namespaceNone. Authentication is the WarpBuild GitHub App installation, so there is no in-cluster credential to store or rotate.
runnerScaleSetNameThe name a workflow targets in runs-onThe runner label. Hosted runners use the published warp- labels; BYOC custom runners use the Runner ID, which is the runner name prefixed with warp-custom-.
scaleSetLabels, rendered as the CRD field runnerScaleSetLabelsExtra labels for multi-label runs-on targetingA single runner label per runner type, plus dynamic labels for per-job features: snapshot.enabled=true, nested-virtualization.enabled=true, network.name=<config>.
runnerGroupWhich organizations and repositories can reach the scale setThe GitHub runner group WarpBuild registers into, default group id 1, selectable in the WarpBuild dashboard under Runners.
maxRunnersConcurrency ceiling, static Helm valueGenerally available Linux and Windows runners have no plan-level concurrency cap to port. Review macOS and beta-runner quotas separately.
minRunnersIdle runner pods held warm on your nodesNothing to configure on hosted runners. On BYOC, standby disks hold a pre-initialized pool per custom runner and boot a job in about 15 seconds.
containerMode.type (dind, kubernetes, kubernetes-novolume)Whether container jobs, service containers, and container actions work, and howNot applicable. Runners are full VMs, so container jobs and service containers work the way they do on GitHub-hosted runners. Docker layer caching and remote Docker builders replace the dind sidecar for image builds.
containerMode.kubernetesModeWorkVolumeClaimPVC shape for Kubernetes mode, needs a dynamic provisionerNot applicable. Each runner gets its own ephemeral encrypted volume, and BYOC instance types with local NVMe get local SSD auto-mount.
template (runner PodSpec: resources, image, securityContext, nodeSelector, volumes)The machine shapeThe runner label picks the shape: 2x through 32x sizes with fixed vCPU, memory, and SSD per label (cloud runners). BYOC custom runners set instance types, fallback instance types, disks, spot, and static IPs.
template.spec.containers[name: runner].imageYour custom runner image, which you build and keep validWarpBuild maintains the hosted images and publishes changes in the docs changelog. BYOC can use custom VM images subject to the documented AMI requirements.
listenerTemplate, listenerMetricsListener pod shape and the metrics you enable and scrapeNot applicable. Observability ships with per-instance metrics and logs and right-sizing recommendations by repository, workflow, job, and instance type.
proxy.http.url, proxy.https.url, proxy.noProxyOutbound proxy for controller, listener, and runnersBYOC places runners in your VPC with your egress path, with static IPs available. For private service access from hosted runners, the Tailscale network addon joins the runner to your tailnet for the duration of the job.
githubServerTLS.certificateFromCustom CA injected into the runner trust storeTODO(verification): no repo doc covers injecting a custom CA into WarpBuild runner trust stores. For GHES estates, the documented path is the enterprise app flow plus allowlisting the control plane egress IPs.
keyVaultAzure Key Vault credential retrieval, public previewNot applicable, since there is no GitHub credential in your infrastructure to fetch.
replicaCount, flags.*, watchSingleNamespace, runnerMaxConcurrentReconciles, rateLimiterController deployment shape and throughput tuningNot applicable. There is no controller in your infrastructure.

Change the runs-on Label

An ARC workflow targets the scale set by its Helm installation name. The quickstart installs arc-runner-set, so workflows read:

jobs:
  build:
    runs-on: arc-runner-set

Multi-label targeting looks like this when runnerScaleSetLabels is set to [linux, gpu, private-network]:

jobs:
  build:
    runs-on: [linux, gpu, private-network]

Both forms are documented in use ARC in a workflow. The WarpBuild replacement is a single label:

 jobs:
   build:
-    runs-on: arc-runner-set
+    runs-on: warp-ubuntu-latest-x64-8x

For BYOC, the label is the Runner ID from the custom runners page, including the required prefix:

 jobs:
   build:
-    runs-on: [linux, private-network]
+    runs-on: warp-custom-ci-stack-runner

Size mapping is direct: an ARC PodSpec requesting 8 CPU and 32 GB maps to warp-ubuntu-latest-x64-8x (8 vCPU, 32 GB, 150 GB SSD). ARM64 jobs map to the warp-ubuntu-latest-arm64-* family, and the full label list with vCPU, memory, storage, and per-minute price is in the cloud runners docs.

Full workflow example

.github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-8x
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Restore dependency cache
        uses: WarpBuilds/cache@v1
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      - run: npm ci
      - run: npm test
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres

  build-arm:
    runs-on: warp-ubuntu-latest-arm64-8x
    steps:
      - uses: actions/checkout@v4
      - run: make build

The service container block is unchanged from the GitHub-hosted form. On ARC the same block requires containerMode set to dind or kubernetes, and Kubernetes mode has an open issue about service containers (issue 1768, open since 2022-09-01).

Cache Migration

What you have on ARC. ARC ships no dependency cache of its own. Dependency caching for ephemeral runners has been an open request since 2023-07-07, and image caching is on GitHub's out-of-scope list for ARC support. In practice ARC teams either use actions/cache against the GitHub Actions cache service and accept its quota and locality, or self-host a cache: an S3 or MinIO bucket behind a cache action, a registry mirror for base images, or a persistent volume the runner pods reuse in Kubernetes mode.

What replaces it. WarpBuild runners include a cache that is enabled by default on Linux runners. Swap the action and keep the same key and path arguments:

-      - uses: actions/cache@v4
+      - uses: WarpBuilds/cache@v1
         with:
           path: ~/.npm
           key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}

WarpBuilds/cache is a drop-in replacement for actions/cache@v4 and supports the split restore and save forms as well (caching docs). Container layer caching is available on hosted runners and on all three BYOC clouds, and remote Docker builders provide dedicated build VMs with a persistent layer cache for image-heavy pipelines.

What to expect on the first runs. Cache entries do not transfer between platforms. The first run of each workflow after the switch writes a fresh cache, so compare durations from the second run onward. If you self-hosted a cache for ARC, keep it reachable until the moved workflows stop referencing it, then remove it during decommissioning.

One action to leave alone. Snapshot runners capture a runner VM mid-workflow and boot later jobs from it, which is a different lever from dependency caching. Add snapshots after the migration is stable rather than during it (snapshot runners).

Run ARC and WarpBuild Side by Side

Both platforms register runners with the same GitHub Actions API, and jobs go wherever the runs-on label points. There is no conflict, no shared state, and no ordering requirement.

  1. Start with one noisy workflow. Pick a workflow that runs often and fails visibly, so signal arrives in hours rather than weeks.
  2. Move it on a branch, then merge. One runs-on change plus the cache action swap.
  3. Compare the second run onward. Job duration, queue time, and failure rate. First runs write a cold cache, so exclude them.
  4. Move in batches by runner size. Every job that requested the same PodSpec resources on ARC moves to the same WarpBuild label together.
  5. Leave the ARC scale sets installed and idle. With minRunners at zero, an idle scale set consumes no runner pods while remaining ready for a rollback.
  6. Track spend on both sides. Your cluster keeps its cost while it stays warm. WarpBuild bills per minute by runner type with no base subscription fee, no platform fee, and no seat fee, and signup includes $10 in free credits, which usually covers the validation batches. Rates are on the pricing page.

Secrets and permissions do not change. Repository and organization secrets, OIDC federation, and GITHUB_TOKEN permissions behave as they do on any self-hosted runner, because the runner still registers against your organization. What disappears is the Kubernetes secret that held your PAT or GitHub App private key.

Rollback

Rollback is a revert of the runs-on line, valid as long as the ARC deployment is still installed. That is the reason the decommissioning section comes last.

  1. Revert the commit that changed runs-on, or change the label back by hand.
  2. If you drained the scale set already, restore maxRunners and minRunners to their previous values and reapply the Helm release.
  3. Leave the cache action swap in place or revert it; either works, since a cold cache is the only cost.

Keep the cluster warm for at least one full release cycle after the last workflow moves, including whatever periodic and scheduled workflows only run weekly or monthly. Those are the jobs that discover a gap after everything else looks green.

Decommission the ARC Deployment

Do this after the full workflow set has run green on WarpBuild, including scheduled workflows. The order below follows GitHub's documented teardown sequence, which is published as the upgrade procedure and applies equally to removal.

1. Drain the queue. Set maxRunners: 0 and minRunners: 0 on each scale set. GitHub documents this state: "If you set both properties to 0, Actions Runner Controller will not create new runner pods when new jobs are available and assigned." Confirm no jobs are still routing to the scale set before continuing.

2. Uninstall every scale-set release. helm uninstall each gha-runner-scale-set installation, one per scale set, and wait for resource cleanup. Watch for pods stuck in Terminating, which is a documented pattern in issue 3010 and issue 4155; the repository troubleshooting guide documents finalizer removal for the legacy equivalent.

3. Uninstall the controller. Remove the gha-runner-scale-set-controller release only after the scale sets are gone.

4. Remove the CRDs. Delete the CRDs in the actions.github.com API group, and, if legacy ARC was ever installed in this cluster, the actions.summerwind.net group as well. Helm does not remove CRDs for you.

5. Delete the leftovers the charts do not own. Each installation generates resources named after the installation with fixed suffixes, documented in the ARC components reference:

LeftoverWhere it came from
INSTALLATION_NAME-gha-rs-github-secretCreated when githubConfigSecret was an object. If you passed a pre-defined secret name as a string, that secret was created by you and is still yours to delete.
-gha-rs-manager, -gha-rs-kube-mode, -gha-rs-no-permissionRoles, RoleBindings, and ServiceAccounts created by the scale-set chart
-gha-rs-controller, -gha-rs-controller-listener, -gha-rs-controller-single-namespaceController ServiceAccount, ClusterRole, ClusterRoleBinding, and the namespace-scoped Roles created when flags.watchSingleNamespace was set
Proxy credential secret referenced by credentialSecretRefCreated by you for authenticated outbound proxying
Custom CA ConfigMap referenced by githubServerTLS.certificateFromCreated by you for enterprise TLS
Namespaces created for ARCRunner namespaces, the controller namespace, and any per-organization namespaces created for isolation

6. Revoke the GitHub credentials. Delete the GitHub App installation ARC used, or revoke the classic PAT. Enterprise-level ARC runners required a classic PAT, so check for one at the enterprise level as well (auth docs).

7. Remove the webhook, only if you ran legacy ARC. Webhook-driven scaling is a legacy-mode feature that installs a separate webhook server through githubWebhookServer.enabled=true, optionally exposed by a NodePort or an ingress (legacy autoscaling docs). Remove that server, its ingress, and the matching GitHub organization or repository webhook. The modern scale-set mode uses an outbound long-poll listener and registers no inbound webhook, so a modern deployment has nothing to remove here. cert-manager is likewise a legacy-mode dependency; the modern charts do not require it.

8. Reclaim the cluster. Delete the node groups or node pools that existed only for runner pods, the storage class and persistent volumes provisioned for Kubernetes mode, the private registry mirror of the controller and runner images, and the log and metric pipelines that only carried ARC telemetry. This step is where the cluster bill actually falls.

9. Clean the GitHub side. Remove empty runner groups that existed only for ARC scale sets, and delete offline self-hosted runner registrations left over from stuck pods.

Compatibility

ARC feature or requirementWarpBuild equivalentStatus
Ephemeral runners, one job per machineEvery runner is an ephemeral VM, created on demand and destroyed after the job (security)Supported
Autoscaling on queue depthDynamic capacity with no plan-level concurrency caps for generally available Linux and Windows runnersSupported
Linux x64 and Linux ARM64Ubuntu 22.04, 24.04, and 26.04 on x64; Ubuntu 24.04 and 26.04 on ARM64, in five sizes eachSupported
Windows runnersWindows Server 2022 and 2025 in four sizes. ARC publishes no Windows image for scale-set mode; the only Windows doc is legacy mode (issue 1001 open since 2021-12-10)Supported, with no ARC counterpart
macOS runnersmacOS 14, 15, and 26 in 6 vCPU and 12 vCPU sizes. ARC documentation does not cover macOS runners, verified on 2026-08-13Supported on WarpBuild's cloud, absent on BYOC
Container jobs and service containersWork as they do on GitHub-hosted runners, with no containerMode to configureSupported
Docker-in-Docker with privileged containersDocker runs on Linux runners directly; remote Docker builders handle image builds with a persistent layer cacheSupported by a different mechanism
Kubernetes mode with PVCsNo persistent volume provisioner to run. Each runner has its own ephemeral encrypted volume, with local NVMe auto-mount on suitable BYOC instance typesNot applicable
Full PodSpec control (nodeSelector, tolerations, topology spread, priority classes)Runner size labels on hosted runners; instance types, fallback instance types, disks, spot, and static IPs on BYOC custom runnersPartial: cluster-scheduling primitives have no equivalent
Runners inside your own networkBYOC creates VMs in your VPC and region, with static IPs available; the Tailscale addon joins hosted runners to your tailnet per jobSupported
No third-party control plane in the build pathWarpBuild operates a control plane, SOC 2 Type 2 certified with evidence at trust.warpbuild.comUnsupported: if policy forbids a vendor control plane, stay on ARC
Custom runner imagesCustom VM images on BYOC, subject to documented AMI requirements. Hosted runner images are maintained by WarpBuildPartial
Custom CA injection into the runner trust storeTODO(verification): no repo doc covers this. GHES estates use the enterprise app flow and control plane egress IP allowlistingUnverified
Runner groups for repository accessRunners register into your GitHub runner group, default id 1, selectable in the dashboardSupported
GitHub Enterprise ServerGHES and GitHub Enterprise Cloud with data residency are supported on the Enterprise plan through the enterprise app flow. ARC requires GHES 3.9 or greaterSupported
Scheduled scaling of warm capacityStandby disks hold a warm pool per BYOC custom runner. ARC has no scheduled scaling (issue 3313)Partial
Metrics and logsObservability with per-instance metrics, logs, and right-sizing recommendations, on by defaultSupported

Questions about a row in this table, or a cluster shape not covered here? Slack support channels are available on demand, and /compare/arc documents the dimension-by-dimension comparison behind these choices. If ARC and WarpBuild are not the only two options on your list, managed alternatives to ARC covers the wider field.

FAQ

How much of my workflow file changes?

The runs-on line, and the cache action if you use one. ARC targets a runner scale set by its Helm installation name or by runnerScaleSetLabels; WarpBuild runners register under warp- labels for hosted runners and warp-custom- labels for BYOC runners. Steps, matrices, services, and secrets are unchanged.

Do I have to delete my ARC deployment before I start?

No, and you should not. Keep the controller and scale sets installed through the parallel-run period so a rollback is a single revert of the runs-on line. Decommission only after the full workflow set has run green on WarpBuild.

What happens to my existing caches?

Caches do not transfer. ARC deployments usually rely on the GitHub Actions cache service or on a cache the platform team self-hosts, since dependency caching for ephemeral runners is still an open request in the ARC repository. On WarpBuild, the first runs after the switch write a fresh cache and later runs restore from it.

Is there a webhook to remove when I decommission?

Only if you ran legacy ARC. Webhook-driven scaling is a legacy-mode feature that installs a separate webhook server through githubWebhookServer.enabled. The modern scale-set mode uses an outbound long-poll listener and registers no inbound webhook, so there is nothing to remove.

Can WarpBuild runners stay inside my own cloud account?

Yes. BYOC runs on AWS, GCP, and Azure, with runner VMs created in your account and region, and a Terraform provider for BYOC on AWS. macOS runners are available on WarpBuild's cloud rather than on BYOC.

What does the parallel-run period cost?

Your cluster keeps costing whatever it costs while it stays warm, and WarpBuild bills the moved jobs per minute by runner type. Signup includes $10 in free credits, which usually covers the validation batches.

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.