Cloud & DevOps |

GitHub Actions Self-Hosted Runners on ARM64: Setup and Cost Savings

How to set up GitHub Actions self-hosted runners on ARM64 instances, with real cost comparisons, Docker multi-arch builds, and performance benchmarks.

By SouvenirList

Your GitHub Actions bill has been creeping up. Every push triggers a workflow that spins up a GitHub-hosted ubuntu-latest runner — an x86_64 VM that costs $0.008/minute for Linux. At 10,000 minutes per month, that’s $80. Not catastrophic, but when you realize that an equivalent ARM64 instance on AWS or Hetzner costs 30–40% less for the same (or better) single-threaded performance, the math starts looking uncomfortable. GitHub Actions self-hosted runners on ARM64 are the highest-leverage CI/CD cost optimization most teams aren’t doing yet in 2026.


TL;DR

  • Self-hosted ARM64 runners cut CI costs by 30–50% compared to GitHub-hosted x86_64 runners.
  • AWS Graviton4 (c8g) and Hetzner Ampere (CAX) instances offer the best price-performance for CI workloads.
  • Setup takes ~30 minutes: launch instance, install runner agent, register with GitHub, add a runs-on label.
  • The main friction is Docker multi-arch builds — you need docker buildx or QEMU, and some base images don’t ship ARM variants.
  • For teams running 5,000+ CI minutes/month, the savings pay for themselves within the first month.

Why ARM64 for CI/CD

ARM64 processors (AWS Graviton, Ampere Altra, Apple Silicon) have reached price-performance parity with x86_64 for most workloads, and for single-threaded tasks — which dominate CI pipelines (lint, test, build) — they often win outright.

The cost advantage comes from two places:

  1. Lower instance pricing. Cloud providers price ARM instances 20–40% below equivalent x86 instances because ARM chips are cheaper to produce and more power-efficient.
  2. No GitHub Actions markup. GitHub-hosted runners include a margin on top of compute costs. Self-hosted runners use your own infrastructure at raw cloud pricing.

Here’s the comparison:

Runner TypevCPURAMCost/minMonthly (10K min)
GitHub-hosted ubuntu-latest (x86)416 GB$0.008$80
AWS Graviton4 c8g.xlarge (ARM64)48 GB~$0.004~$40
Hetzner CAX21 (ARM64)48 GB~$0.002~$20
Self-hosted Mac Mini M4 (ARM64)1016 GB~$0.003~$30 (amortized)

The Hetzner numbers are particularly striking — their Ampere-based CAX instances in European datacenters cost a fraction of US cloud providers for equivalent specs.


Setting Up a Self-Hosted ARM64 Runner

Step 1: Launch an ARM64 Instance

AWS Graviton (recommended for US teams):

aws ec2 run-instances \
  --image-id ami-0a1b2c3d4e5f6g7h8 \
  --instance-type c8g.medium \
  --key-name my-key \
  --security-group-ids sg-xxxxx \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=gh-runner-arm64}]'

Use the latest Ubuntu 24.04 ARM64 AMI. Graviton4 c8g instances offer the best CI price-performance — c8g.medium (2 vCPU, 4 GB) handles most single-repo workflows.

Hetzner (recommended for cost-sensitive teams):

Create a CAX11 (2 vCPU, 4 GB, ~€3.49/mo) or CAX21 (4 vCPU, 8 GB, ~€6.49/mo) via the Hetzner Cloud Console. Select Ubuntu 24.04 ARM64.

Step 2: Install the GitHub Actions Runner Agent

SSH into your instance and run the official setup script:

# Create a runner directory
mkdir actions-runner && cd actions-runner

# Download the latest ARM64 runner
curl -o actions-runner-linux-arm64.tar.gz -L \
  https://github.com/actions/runner/releases/latest/download/actions-runner-linux-arm64-2.321.0.tar.gz

tar xzf actions-runner-linux-arm64.tar.gz

# Configure — get the token from your repo Settings > Actions > Runners
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
  --token YOUR_REGISTRATION_TOKEN \
  --labels arm64,self-hosted,linux \
  --name arm64-runner-01

# Install and start as a systemd service
sudo ./svc.sh install
sudo ./svc.sh start

The --labels flag is critical. You’ll reference these labels in your workflow files.

Step 3: Update Your Workflow

Change runs-on from the GitHub-hosted label to your custom label:

jobs:
  build:
    runs-on: [self-hosted, linux, arm64]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm test
      - run: npm run build

That’s it. Your workflow now runs on your ARM64 instance instead of GitHub’s x86 fleet.


Docker Multi-Arch: The Main Friction Point

If your CI pipeline builds Docker images, switching to ARM64 means your images are now ARM64-native. This is fine if your production environment is also ARM64 (Graviton on ECS/EKS, Fly.io, etc.), but if you deploy to x86_64, you need multi-arch builds.

Option A: Build for Both Architectures with buildx

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build multi-arch image
  uses: docker/build-push-action@v6
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ghcr.io/your-org/your-app:latest

This uses QEMU emulation for the non-native platform. The ARM64 build runs natively (fast), and the x86_64 build runs under emulation (slower — typically 2–4x). For most projects, the total multi-arch build time is still less than a single-arch build on a GitHub-hosted runner.

Option B: Use Two Runners (Matrix Strategy)

For large builds where QEMU emulation is too slow:

jobs:
  build:
    strategy:
      matrix:
        include:
          - runner: [self-hosted, linux, arm64]
            platform: linux/arm64
          - runner: [self-hosted, linux, x64]
            platform: linux/amd64
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: docker/build-push-action@v6
        with:
          platforms: ${{ matrix.platform }}
          push: true

Each architecture builds natively on its own runner — no emulation overhead.

Watch Out For: Base Image Availability

Most popular base images (node, python, ubuntu, alpine, debian) ship ARM64 variants. But some niche images don’t. Before migrating, verify:

docker manifest inspect node:22-alpine | grep arm64

If your base image doesn’t support ARM64, you’ll need to find an alternative or build your own.


Security and Maintenance

Self-hosted runners introduce operational responsibility that GitHub-hosted runners abstract away:

  • Isolation. GitHub-hosted runners are ephemeral — each job gets a fresh VM. Self-hosted runners are persistent by default, meaning secrets and artifacts from one job can leak to the next. Use ephemeral mode (--ephemeral flag) for sensitive repos.
  • Updates. The runner agent auto-updates, but your OS doesn’t. Schedule weekly apt update && apt upgrade via cron or use an AMI pipeline.
  • Network security. Restrict inbound access to SSH only. The runner agent connects outbound to GitHub — no inbound ports needed for Actions.
  • Secrets. Never store repo secrets on the runner filesystem. Use GitHub’s encrypted secrets or a vault. Self-hosted runners on public repos are a security risk — anyone can submit a workflow via PR.

When NOT to Use Self-Hosted ARM64

  • Tiny teams with <1,000 CI minutes/month. The operational overhead (patching, monitoring, restarts) isn’t worth the ~$10/month savings.
  • Public repos without strict fork controls. A malicious PR can run arbitrary code on your self-hosted runner. GitHub-hosted runners are safer here.
  • Workflows that depend on x86-specific binaries. If your test suite shells out to x86-only tools that don’t have ARM builds, you’ll hit compatibility walls.
  • Mac/Windows CI needs. ARM64 Linux runners are cheap and easy. ARM64 Mac runners (Mac Mini M4) work but require macOS-specific setup. ARM64 Windows is still immature.

For more on CI/CD architecture decisions, see our CI/CD pipeline guide and zero-downtime deployment strategies.


FAQ

How many runners do I need?

One runner handles one job at a time. If your team triggers 3–5 concurrent workflows, you need 3–5 runners. Start with one and add more as queue times increase. Auto-scaling with actions-runner-controller on Kubernetes is the production-grade approach.

Will GitHub-hosted ARM64 runners make self-hosting obsolete?

GitHub offers ARM64 hosted runners (ubuntu-24.04-arm) in beta as of 2026, but they’re priced at $0.005/minute — still 2–3x more than self-hosted on Hetzner or AWS spot instances. For cost-sensitive teams, self-hosting remains cheaper.

Can I use AWS Spot instances for runners?

Yes, and you should for non-critical workflows. Graviton spot instances are 60–70% cheaper than on-demand. Use a launch template with spot request and the --ephemeral runner flag so interrupted jobs are automatically retried.

What about Apple Silicon (M-series) as runners?

Mac Minis with M4 chips make excellent self-hosted runners for iOS/macOS CI. For Linux-only workloads, cloud ARM64 instances are more cost-effective because you’re not paying for macOS hardware you don’t need.

How do I monitor runner health?

The GitHub API exposes runner status. Set up a simple health check:

curl -s -H "Authorization: token $GH_TOKEN" \
  https://api.github.com/repos/ORG/REPO/actions/runners \
  | jq '.runners[] | {name, status, busy}'

Alert if any runner shows status: "offline" for more than 5 minutes.


Bottom Line

Self-hosted GitHub Actions runners on ARM64 deliver 30–50% CI cost savings with minimal setup friction. The sweet spot is teams running 5,000+ CI minutes per month on Linux workloads — one Hetzner CAX21 at €6.49/month replaces $40+/month of GitHub-hosted compute. The only real gotcha is Docker multi-arch builds, and docker buildx handles that cleanly. If your CI bill is growing and your workloads are Linux-native, this is the single highest-ROI infrastructure change you can make this quarter.

Product recommendations are based on independent research and testing. We may earn a commission through affiliate links at no extra cost to you.

Tags: github actions arm64 self-hosted runners ci/cd arm64 github actions cost

Related Articles