Saltar al contenido

GitHub Actions self-hosted runner for the whole org

Para CTO y desarrolladoresDecisiones de CTO

Filip PapranecFounder, Digitaliko

GitHub Actions self-hosted runner for the whole org. Illustration: many repositories sending jobs to one runner box, with the Docker socket crossed out and a fallback arrow to GitHub-hosted runners.GitHub Actions self-hosted runner for the whole org. Illustration: many repositories sending jobs to one runner box, with the Docker socket crossed out and a fallback arrow to GitHub-hosted runners.

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 runnerSelf-hosted runner
MachineA fresh VM per job, managed by GitHubYour server, VM or computer
Clean stateEvery job starts cleanNot guaranteed, files and processes can persist
SoftwareGitHub's imageWhatever you install
NetworkPublic internetCan reach your private network
Who maintains itGitHubYou: updates, disk, security
BillingActions minutes on your planYour 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:

  1. 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.

  2. Set the organization variable

    CI_RUNNER_LABEL = self-hosted, visible to all repositories.

  3. 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.

  4. 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.

GitHub Docs
Secure use reference, retrieved 2026-10-24

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.

How to wire a self-hosted GitHub runner. Every workflow says runs-on vars.CI_RUNNER_LABEL or ubuntu-latest. Org repositories with the variable run on a runner registered at organization scope, which serves CI and agent jobs: validate, code review and verify, with no Docker socket. Repositories of another organization have no variable and run on GitHub-hosted ubuntu-latest. Production web servers are a separate machine.How to wire a self-hosted GitHub runner. Every workflow says runs-on vars.CI_RUNNER_LABEL or ubuntu-latest. Org repositories with the variable run on a runner registered at organization scope, which serves CI and agent jobs: validate, code review and verify, with no Docker socket. Repositories of another organization have no variable and run on GitHub-hosted ubuntu-latest. Production web servers are a separate machine.

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@v4
    with:
      node-version: 22
  - uses: oven-sh/setup-bun@v2
  - shell: bash
    run: 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.

The AI review of every pull request is in Claude Code review: AI code review in CI. How far to let an agent go on its own machine is in Claude Code dangerously skip permissions.

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.

If you'd rather talk first, book a 15-minute intro call.

If your runner died tonight, how many workflow files would you have to edit tomorrow?

In short

  • A runner registered at organization scope can serve many repositories, runner groups decide which.
  • runs-on: ${{ vars.CI_RUNNER_LABEL || 'ubuntu-latest' }} picks it, and one variable sends everything back to hosted runners.
  • No Docker socket: whoever controls the daemon is root on the box.
  • Client-owned repositories and production stay off the runner.

Frequently asked questions

What is a GitHub Actions self-hosted runner?

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.

ChatGPTClaudePerplexity
Engineering

Más artículos

Decisiones de CTO

Refactor or rewrite from scratch? How to decide

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.

Decisiones de CTO

Technical debt slowing development: what to fix first

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.

Decisiones de CTO

Body shopping or a senior developer in your team?

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.

Contacto

  • Digitaliko s.r.o.
    Šípková 377/17
    958 03 Malé Uherce, Eslovaquia
  • Póngase en contacto
    info@digitaliko.com
    +421 950 595 506
    WhatsApp