CircleCI self-hosted runners: resource classes, machine runner 3 and container runner
A CircleCI self-hosted runner is a process on your own machine or Kubernetes cluster that picks up jobs for a resource class named namespace/name. You create the resource class, keep its token, install machine runner 3 on a Linux, macOS or Windows host (or container runner on Kubernetes with Helm), and point jobs at it with resource_class:. Self-hosted runners work on the Free, Performance and Scale cloud plans.
Machine runner versus container runner
CircleCI ships two runner types. Both connect outbound to CircleCI and claim jobs for one or more resource classes, so your hosts need no inbound ports.
| Machine runner 3 | Container runner | |
|---|---|---|
| Runs on | VMs, bare metal, Docker | Kubernetes |
| Job environment | The host itself, with its installed tools | A fresh pod per job from a Docker image |
| Executor in config | machine: true | docker: with an image |
| Scaling | You add machines | Spawns pods on demand |
| Platforms | Linux (x86_64, ARM64, s390x, ppc64le), macOS 11.2+ (Intel and Apple silicon), Windows Server 2019+ | Kubernetes on x86_64 or ARM64 |
CircleCI positions container runner as a complement to machine runner. Use machine runner when the job needs the full host (macOS signing, Windows toolchains, hardware access, Docker builds with a local daemon). Use container runner for Linux jobs that already run in CircleCI convenience images or your own images.
When self-hosting makes sense
Self-hosted runners fit jobs that need private network access, GPUs, architectures CircleCI's cloud does not offer, or licensed software tied to specific hosts. They also let you keep build artifacts and caches inside your own network. If none of that applies, CircleCI's cloud executors involve less work for you.
Plan availability and billing
CircleCI lists self-hosted runners on the Free, Performance and Scale cloud plans. Runner execution does not consume credits, but your account needs at least one credit to use runners, and storage and network usage can still cost credits. Concurrency limits for runners differ by plan; check CircleCI's pricing page for the current numbers before you size a fleet. CircleCI Server (the self-managed product) supports runners too, with an extra API URL setting shown below.
Create a resource class and token
A resource class is the label jobs use to find your runners. Its full name is namespace/resource-class, where the namespace belongs to your organization. Namespaces allow lowercase letters, numbers, underscores and dashes. Resource class names also allow uppercase letters, colons and plus signs.
You can create one in the web app under Self-Hosted Runners, or with the CircleCI CLI:
circleci runner resource-class create my-org/linux-x86-large "Linux x86 build hosts" --generate-token
The command prints a resource class token. CircleCI shows it once, so store it in your secret manager. Every runner that serves this resource class authenticates with that token. Create separate resource classes (and tokens) for separate hardware or trust levels, for example my-org/linux-arm64 and my-org/macos-signing.
Install machine runner 3 on Linux
On Debian or Ubuntu, add CircleCI's package repository and install circleci-runner. The commands follow the official Linux guide:
curl -s https://packagecloud.io/install/repositories/circleci/runner/script.deb.sh?any=true | sudo bash
sudo apt-get install -y circleci-runner
The config file lives at /etc/circleci-runner/circleci-runner-config.yaml. Put the token in place and set a runner name:
export RUNNER_AUTH_TOKEN="your-resource-class-token"
sudo sed -i "s/<< AUTH_TOKEN >>/$RUNNER_AUTH_TOKEN/g" /etc/circleci-runner/circleci-runner-config.yaml
The relevant keys in the file:
runner:
name: "build-host-01"
cleanup_working_directory: true
api:
auth_token: "your-resource-class-token"
Set runner.working_directory too if you want jobs to run somewhere other than the package default; the runner needs write access to that path.
Enable and start the service:
sudo systemctl enable circleci-runner && sudo systemctl start circleci-runner
The runner shows up on the resource class page in the web app once it connects. Install the tools your jobs need (compilers, Docker, language runtimes) on the host yourself; machine runner does not pull images for machine: true jobs.
Install on macOS
CircleCI publishes machine runner 3 through Homebrew:
brew tap circleci-public/circleci
brew install circleci-runner
Edit $HOME/Library/Preferences/com.circleci.runner/config.yaml and set runner.name and api.auth_token. Then load the launch agent in the GUI domain:
launchctl bootstrap gui/$(id -u) $HOME/Library/LaunchAgents/com.circleci.runner.plist
launchctl enable gui/$(id -u)/com.circleci.runner
launchctl kickstart -k gui/$(id -u)/com.circleci.runner
The GUI domain gives jobs access to the logged-in session, which UI tests and code signing with the login keychain need. For a headless host, move the plist to /Library/LaunchAgents/ and use the user/$(id -u) domain instead, as the macOS guide describes.
Install on Windows
On Windows Server 2019 or newer, download Install-CircleCIRunner.ps1 from the CircleCI-Public/runner-installation-files repository and run it from an elevated PowerShell:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
./Install-CircleCIRunner.ps1
The installer opens the runner config file in Notepad. Fill in the name and token, save, and the runner starts. The files live under C:\Program Files\CircleCI. CircleCI recommends the default single-task mode on Windows for reliability.
Run machine runner 3 in Docker
CircleCI documents running machine runner 3 inside a container you build from their Dockerfile. Pass the name and token as environment variables:
CIRCLECI_RUNNER_NAME=docker-runner-01 \
CIRCLECI_RUNNER_API_AUTH_TOKEN=your-resource-class-token \
docker run --env CIRCLECI_RUNNER_NAME --env CIRCLECI_RUNNER_API_AUTH_TOKEN \
--name circleci-runner <image-id>
Passing --env NAME without a value keeps the token out of the ps output. On CircleCI Server, add CIRCLECI_RUNNER_API_URL with your server's hostname.
Container runner on Kubernetes
Container runner runs as a deployment in your cluster and creates one pod per job. Install it with Helm into a circleci namespace:
kubectl create namespace circleci
helm repo add container-agent https://packagecloud.io/circleci/container-agent/helm
helm repo update
Write a values.yaml that maps each resource class to its token:
agent:
resourceClasses:
my-org/k8s-linux:
token: <resource-class-token>
helm install container-agent container-agent/container-agent -n circleci -f values.yaml
One deployment can serve several resource classes. Each pod starts from the image the job names, runs the job and terminates, so container runner jobs get a clean environment by default. For production, create your own Kubernetes Secret with one key per resource class (the class name with / replaced by .) and name it in agent.customSecret. Keep each class listed under resourceClasses as an empty map so the chart still knows about it.
Target the runner from config.yml
A machine runner job sets machine: true and the resource class:
version: 2.1
jobs:
build:
machine: true
resource_class: my-org/linux-x86-large
steps:
- checkout
- run: make test
workflows:
main:
jobs:
- build
A container runner job sets a Docker image and the resource class:
jobs:
test:
docker:
- image: cimg/base:2024.01
resource_class: my-org/k8s-linux
steps:
- checkout
- run: make test
A job whose resource class has no connected runner waits in the queue until one appears.
Single-task mode and ephemeral machines
Machine runner 3 defaults to runner.mode: continuous, which keeps polling for jobs on the same host. Set runner.mode: single-task (environment variable CIRCLECI_RUNNER_MODE) to make the runner process exit after one job. The machine stays up and systemd restarts the process, so for a true one-job-per-VM setup you add a shutdown command to the systemd unit or let your autoscaler destroy the instance when the process exits.
Two more settings help with autoscaled fleets:
runner.idle_timeout(CIRCLECI_RUNNER_IDLE_TIMEOUT): the runner exits if it claims no task within the given duration, such as10m. The default is no timeout.runner.max_run_time(CIRCLECI_RUNNER_MAX_RUN_TIME): overrides the 5-hour default job limit.
CircleCI does not ship a managed VM autoscaler for machine runner. Its scaling guide has you poll the runner API's unclaimed-tasks endpoint per resource class, set a cloud autoscaling group's desired size from that count, and run single-task runners with an idle timeout so instances recycle themselves. CircleCI's blog has a worked AWS Auto Scaling example. Container runner scales pods on its own, and you pair it with a cluster autoscaler for nodes.
Security hardening
- One token per trust level. A resource class token lets any machine claim that class's jobs. Keep deploy-capable runners in their own resource class and token.
- Forked pull requests. Jobs from forks can run arbitrary code on your host. Disable building forked PRs in project settings, or send them to a resource class backed by single-task, throwaway machines without secrets.
- Clean workspaces. Set
cleanup_working_directory: trueso checkouts do not leak into the next job on a continuous-mode host. - Contexts for secrets. Store secrets in CircleCI contexts with security group restrictions instead of baking them into the runner host.
Troubleshooting
Jobs queue forever
The resource_class in config.yml must match the full namespace/name, character for character. Check the resource class page for connected runners, and on Linux read journalctl -u circleci-runner.
Runner fails to authenticate
The token belongs to a different resource class or someone regenerated it. Create a new token for the class and update api.auth_token.
"Command not found" in machine jobs
Machine runner uses the host's tools. Install the missing tool on the host, or switch the job to container runner with an image that contains it.
macOS jobs cannot reach the keychain or simulator
The runner runs in the user domain without a GUI session. Load it in the gui/$(id -u) domain with a logged-in user.
FAQ
Are CircleCI self-hosted runners free?
Runner execution does not consume credits on the Free, Performance and Scale plans. You still need at least one credit on the account, and you pay your own infrastructure costs.
Can I use Docker images with a CircleCI machine runner?
Not as the job executor. Machine runner jobs run on the host. You can install Docker on the host and call docker from steps, or use container runner, which runs each job in the image you specify.
Does CircleCI support ARM64 self-hosted runners?
Yes. Machine runner supports ARM64 Linux and Apple silicon Macs, and container runner supports ARM64 Kubernetes nodes.
How does this compare to GitHub or GitLab runners?
The model matches: a token scoped to a label, an agent that polls outbound, and a one-job mode for isolation. See the guides for GitHub Actions self-hosted runners and Buildkite agents.
Running CircleCI runners with cirunner.dev
cirunner.dev provisions a fresh VM per job, registers it with your CI using a token you provide, scales with the queue and destroys the machine after the job. You choose CPU, RAM, x86_64 or arm64, region, base image and an optional GPU, and pay per compute minute.
The service is in early access with GitHub Actions and GitLab CI as launch platforms. CircleCI is on the roadmap; vote for it on the early-access form to get single-task CircleCI runners without building your own autoscaler.
Skip the runner fleet
cirunner.dev boots a fresh VM for every CircleCI job, registers it, and destroys it when the job ends. CircleCI is on our roadmap. Vote for it on the early-access form.
Join early access