Self-Hosted Runners
Self-hosted runners give you complete control over your GitHub Actions execution environment. Run workflows on your own infrastructure, access private networks, use specialized hardware, and scale to meet your organization's needs—all while maintaining security and compliance.
Self-hosted runners are machines that you manage and operate, which run GitHub Actions workflows. Unlike GitHub-hosted runners, which run on GitHub's infrastructure, self-hosted runners run on your own servers, cloud instances, or even local machines. You have full control over the operating system, installed software, hardware specifications, and network access.
Organizations choose self-hosted runners for several reasons. Some need access to private networks—databases, internal APIs, or on-premises infrastructure that GitHub-hosted runners cannot reach. Others require specialized hardware like GPUs for machine learning workloads, or large memory configurations for memory-intensive builds. Many organizations also use self-hosted runners to reduce costs, especially for large-scale CI/CD operations where GitHub-hosted minutes can become expensive.
Access to private networks: If your workflows need to deploy to internal servers, access private databases, or interact with on-premises infrastructure, self-hosted runners are essential. GitHub-hosted runners cannot access private networks behind your firewall.
Specialized hardware: Machine learning training needs GPUs. iOS development needs macOS hardware. Performance testing needs large memory or specific CPU architectures. Self-hosted runners let you bring your own hardware.
Cost control: For organizations running thousands of workflow minutes per month, GitHub-hosted runner costs can add up. Self-hosted runners on your existing cloud infrastructure can be more economical, especially if you already have reserved instances or spot capacity.
Custom environments: You need specific versions of tools, libraries, or operating systems not available on GitHub-hosted runners. Self-hosted runners can run any OS and have any software pre-installed.
Compliance and data residency: Some organizations have regulatory requirements that data must not leave certain geographic regions or must remain within their infrastructure. Self-hosted runners give you control over where workflows execute.
Setting up a self-hosted runner is straightforward. Go to your repository, organization, or enterprise settings, navigate to Actions → Runners, and click "New runner." GitHub provides a script tailored to your operating system that downloads the runner software and registers it with your account.
The runner software is a small application that polls GitHub for jobs, executes them, and reports results. It runs on Linux, Windows, macOS, and even ARM architectures. Once installed and registered, the runner appears in your list and is ready to accept jobs. You can configure multiple runners to handle parallel workloads.
# Installing a runner on Ubuntu (example)
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.320.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.320.0/actions-runner-linux-x64-2.320.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.320.0.tar.gz
./config.sh --url https://github.com/owner/repo --token AAAAAAAA
./run.sh
As you scale, you'll want to organize your runners. Runner groups allow you to create collections of runners with similar characteristics. For example, you might have a group for "linux-production," another for "windows-testing," and a third for "gpu-ml."
Labels are how workflows select which runner to use. When you add a runner, you can assign custom labels. In your workflow, you specify `runs-on: self-hosted` to use any self-hosted runner, or `runs-on: [self-hosted, linux, gpu]` to require specific labels. This gives you fine-grained control over job routing.
# Workflow targeting specific runner labels
jobs:
ml-training:
runs-on: [self-hosted, linux, gpu]
steps:
- run: python train_model.py
build-windows:
runs-on: [self-hosted, windows, build]
steps:
- run: msbuild MyProject.sln
Ephemeral runners are self-hosted runners that are destroyed after each job. This is a critical security and reliability feature. With ephemeral runners, each workflow runs in a fresh, clean environment. No leftover files, no cached credentials, no cross-job contamination.
To configure ephemeral runners, add `--ephemeral` when configuring the runner. The runner will execute exactly one job, then shut down. You then need a mechanism to provision new runners—typically using autoscaling groups, Kubernetes, or cloud VM provisioning.
Ephemeral runners are the recommended best practice for self-hosted runners, especially for organizations with multiple repositories or untrusted workflows. They prevent one job from affecting another and ensure consistent, reproducible builds. Many organizations use tools like actions-runner-controller for Kubernetes, or Terraform to auto-scale runners in the cloud.
# Configure ephemeral runner (destroys after one job)
./config.sh --url https://github.com/owner/repo --token AAAAAAAA --ephemeral
./run.sh
# Example autoscaling with AWS and Terraform
resource "aws_autoscaling_group" "github_runners" {
desired_capacity = 0
min_size = 0
max_size = 50
...
}
Use ephemeral runners whenever possible. This is the single most important security practice. Ephemeral runners eliminate persistent state and reduce the attack surface.
Run runners in isolated environments. Use separate VMs, containers, or sandboxes for each runner. Don't run multiple runners on the same machine unless you trust all workflows equally.
Minimize permissions. Runners should have the minimum permissions needed to execute workflows. Use service accounts with scoped permissions. Never give runners broad access to production infrastructure.
Keep runner software updated. GitHub regularly releases new runner versions with security patches. Update your runners regularly or use automated update mechanisms.
Use private networking. If runners need to access internal resources, put them in a private subnet or VPC. Use firewalls to restrict inbound and outbound traffic.
Audit runner access. Monitor who has access to register new runners. Runner registration tokens can expose your infrastructure if compromised. Use organization-level runners for better control.
For organizations with variable workload, autoscaling is essential. Instead of keeping many runners idle, you scale up when jobs arrive and scale down when idle. Several approaches exist:
Kubernetes with actions-runner-controller: This popular open-source project runs runners as Kubernetes pods. It automatically scales based on pending jobs and destroys pods after each run. Works on any Kubernetes cluster—EKS, AKS, GKE, or self-managed.
VM autoscaling groups: Use cloud provider autoscaling groups (AWS Auto Scaling, Azure VM Scale Sets, Google Instance Groups). Configure a custom script that registers each new VM as a runner and shuts it down after the job completes.
Serverless ephemeral runners: Some organizations use AWS Lambda or similar serverless platforms to run short-lived jobs, though this is less common due to time limits.
Third-party solutions: Tools like GitHub Actions Runner Controller (ARC) for Kubernetes, or self-hosted runner autoscaling scripts from the community provide robust scaling solutions.
# Example: Kubernetes deployment with actions-runner-controller
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: my-runners
spec:
replicas: 0
template:
spec:
repository: myorg/myrepo
ephemeral: true
resources:
limits:
cpu: 2
memory: 4Gi
GitHub-Hosted Runners: No maintenance, always up to date, secure isolation per job, limited to available operating systems (Ubuntu, Windows, macOS), limited hardware options, cost per minute for private repositories.
Self-Hosted Runners: You manage everything, any OS or hardware, access to private networks, potential cost savings at scale, need to maintain security, requires capacity planning, ephemeral runners recommended.
Many organizations use a hybrid approach: GitHub-hosted runners for public repositories and general-purpose builds, and self-hosted runners for specialized workloads, private network access, or high-volume internal CI/CD.
Running self-hosted infrastructure requires ongoing attention. Set up monitoring for runner availability, job queue length, and runner health. Use tools like Prometheus, Grafana, or cloud monitoring to track metrics.
Regularly update the runner software. New versions are released frequently with bug fixes and features. Automate updates where possible, or schedule regular maintenance windows.
Monitor disk space. Build artifacts, Docker images, and package caches can accumulate. Ephemeral runners solve this automatically. For persistent runners, implement cleanup scripts to remove old files.
Track costs. If you're running runners in the cloud, monitor your cloud spending. Autoscaling helps, but misconfigured scale groups can lead to unexpected bills.
# Example health check for runner
#!/bin/bash
if ! systemctl is-active --quiet actions-runner; then
echo "Runner is down. Restarting..."
systemctl restart actions-runner
fi
Self-hosted runners give you the freedom to run GitHub Actions anywhere. With ephemeral runners and autoscaling, you get the best of both worlds: control and scale.