A GitHub Actions self-hosted runner is a machine you manage that picks up jobs from your workflows. A repository can pick it with one organization variable and fall back to GitHub-hosted runners without it.
We recommend three decisions. Register the runner at organization scope, not on one repository. Do not give it access to the Docker socket. And keep repositories from another organization, such as client-owned ones, off it.
What is a self-hosted runner and when is it worth it?
GitHub's hosted runners are fresh virtual machines that GitHub starts for each job. A self-hosted runner is your own machine running GitHub's runner application. You install it, keep it updated and decide what it may reach.
GitHub-hosted runner
Self-hosted runner
Machine
A fresh VM per job, managed by GitHub
Your server, VM or computer
Clean state
Every job starts clean
Not guaranteed, files and processes can persist
Software
GitHub's image
Whatever you install
Network
Public internet
Can reach your private network
Who maintains it
GitHub
You: updates, disk, security
Billing
Actions minutes on your plan
Your own machine
A self-hosted runner pays off when jobs run often, when they need something the hosted image does not have, or when long agent jobs would eat hosted minutes. A typical case is CI that runs a validator, an AI code review and an AI verification step on most pull requests.
How do you pick the runner per repository?
Do not name the runner in any workflow. Every workflow template has the same line:
jobs:validate:runs-on:${{ vars.CI_RUNNER_LABEL || 'ubuntu-latest' }}steps: - uses:actions/checkout@v4 - run:npm ci && npm test
CI_RUNNER_LABEL is an organization variable set to self-hosted, visible to the repositories allowed to use the runner. When it is set, the job goes to your runner. When it is missing, the expression falls back to ubuntu-latest.
That fallback is the whole point. If the box dies, one edit of the variable sends every repository back to hosted runners. Nobody edits a single workflow file.
Setting it up takes four steps:
01
Register at organization scope
Add the runner under the organization's settings, not under one repository, so every repository in the org can send it jobs.
02
Set the organization variable
CI_RUNNER_LABEL = self-hosted, visible to all repositories.
03
One runs-on line everywhere
Every workflow template uses the expression with the ubuntu-latest fallback. The templates are the only place it is written.
04
Keep the runner bare
Shared actions install Node and Bun themselves with setup-node and setup-bun, so the box needs almost nothing preinstalled.
GitHub documents the organization option directly: runners added "at the organization level, where they can be used to process jobs for multiple repositories in an organization" (GitHub docs, as of 2026-10-24).
Why not give the runner the Docker socket?
Because a CI runner runs code from pull requests. GitHub is blunt about what that means:
Self-hosted runners for GitHub do not have guarantees around running in ephemeral clean virtual machines, and can be persistently compromised by untrusted code in a workflow.
Add the Docker socket and it gets worse. Docker's own security page says "only trusted users should be allowed to control your Docker daemon", because a container can mount the host's root directory (Docker docs, as of 2026-10-24). A workflow step that can reach the socket can start such a container and become root on the box.
So do not mount the host socket into the runner. Jobs that need service containers then fail at Initialize containers. That is fine: the failure is visible, and a daemon of their own fixes it.
What belongs on the runner and what does not?
CI and agent work only, such as a validator, an AI code review and an AI verification step. Not production. How the verification step judges a pull request is in LLM as a judge.
Keep production web servers on a separate machine. Builds next to production would compete with it for resources.
Keep the runner itself bare. A verify action can start like this, because the reviewer needs Node and the repository scripts need their package manager:
steps:# the self-hosted runner is bare: the reviewer needs node, the repo scripts need their package manager - uses:actions/setup-node@v4with:node-version:22 - uses:oven-sh/setup-bun@v2 - shell:bashrun:corepack enable
Every job declares what it needs. A job that works on your own runner also works on ubuntu-latest, which is what makes the fallback safe.
Why keep repositories of another organization on hosted runners?
If you also work on repositories that belong to a client in the client's own GitHub organization, keep their workflows on runs-on: ubuntu-latest. Three reasons:
It is their code and their CI. A client who owns the repository should not depend on a machine a vendor runs. If the work ends, their CI keeps running without a single change.
An organization runner belongs to its organization. It serves the repositories of that organization. Reaching across organizations means a second registration and a second trust boundary.
Less to explain. Nobody has to ask what runs their code, or where.
Write the rule into your CI docs. Repositories in your organization follow the org variable, client-owned repositories stay on ubuntu-latest.
Is a self-hosted runner right for your organization?
Go through what you have today:
The last point is the limit that bites first. One runner process takes one job at a time, and everything else waits. Short jobs hide it. Long agent jobs will not. More runner processes on the same box are the cheaper fix, before a bigger machine.
Need CI that a whole team and its agents can rely on?
We set up runners, shared CI actions and the review around them for development teams as part of a senior developer for your team. The agent side is covered by AI-native SDLC for development teams. AI writes most of our code. A senior reviews every change and is accountable for production. Since 2023 we have delivered 15+ production projects.
We get back to you within two business days with a few questions about your work. Then we agree on what we do first.
A machine you manage that runs the GitHub Actions runner application and picks up jobs from your workflows. It can be a cloud server, a VM or a computer in your office. You choose it in a workflow with runs-on and its labels.
Can one self-hosted runner serve every repository in an organization?
Yes. A runner registered at the organization level can process jobs for multiple repositories in that organization. Runner groups let you limit which repositories may use it.
Is a self-hosted runner safe for public repositories?
GitHub says self-hosted runners should almost never be used for public repositories, because anyone can open a pull request and run code on the machine. Even on private repositories, anyone who can open a pull request can run code there.
How do I switch between a self-hosted and a GitHub-hosted runner?
Use an expression in runs-on: runs-on: ${{ vars.CI_RUNNER_LABEL || 'ubuntu-latest' }}. With the variable set, jobs go to your runner. Without it, they fall back to GitHub-hosted Ubuntu. One edit of an organization variable moves every repository.
Why not mount the Docker socket into the runner?
Whoever can talk to the Docker daemon can start a container with the host file system mounted and become root on the machine. On a CI runner that is anyone who can open a pull request. If a job needs containers, give it its own daemon instead.
Pregunte a una IA sobre este artículo
La IA también tiene en cuenta nuestros otros artículos y la experiencia de proyectos reales.
Refactor when the code does the right thing badly. Rewrite when it does the wrong thing or you do not own it. A decision table for CTOs and a way to do it without stopping the roadmap.
You can measure technical debt from git history and bugs, without an expensive tool. Fix the code that changes most and blocks the roadmap first, not the ugliest code. A guide for CTOs.
Body shopping gives you developer hours you manage yourself. A senior in your team gives you delivered tasks and a team that learns to work with AI. Compared with outsourcing and hiring, for CTOs.
Es hora de dejar de planificar y empezar a resolver.