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.
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-onlabel. - The main friction is Docker multi-arch builds — you need
docker buildxor 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:
- 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.
- 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 Type | vCPU | RAM | Cost/min | Monthly (10K min) |
|---|---|---|---|---|
GitHub-hosted ubuntu-latest (x86) | 4 | 16 GB | $0.008 | $80 |
AWS Graviton4 c8g.xlarge (ARM64) | 4 | 8 GB | ~$0.004 | ~$40 |
| Hetzner CAX21 (ARM64) | 4 | 8 GB | ~$0.002 | ~$20 |
| Self-hosted Mac Mini M4 (ARM64) | 10 | 16 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 (
--ephemeralflag) for sensitive repos. - Updates. The runner agent auto-updates, but your OS doesn’t. Schedule weekly
apt update && apt upgradevia 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.