3.2.2. Runner Groups, IP Allow Lists, and Networking
💡 First Principle: A runner group is an access-control boundary around compute — it answers "which repositories and workflows may schedule jobs on these machines?" — and it is the only mechanism that prevents any repository in an organization from targeting any self-hosted runner it can name.
Without groups, a runs-on: [self-hosted] in any repository lands on whatever matching runner is free, including the one sitting in your production network. Groups fix that. Every runner belongs to exactly one group (the Default group if unspecified), and each group carries an access policy: which organizations (at enterprise level), which repositories (all, private only, or a selected list), and — the detail people forget — which workflows may use it, letting you restrict a group to specific reusable workflows only. Runner groups are available for organizations and enterprises; larger hosted runners are also organized into groups, so the same access model covers both fleets.
Network controls come in two directions. Inbound, an IP allow list at the organization or enterprise level restricts which addresses may reach your GitHub resources; when you use it with GitHub-hosted runners you must enable the option to automatically add the hosted runners' addresses, or your own workflows lock themselves out — a favorite scenario. Outbound, self-hosted runners need egress to GitHub's endpoints (github.com, api.github.com, *.actions.githubusercontent.com, plus package and artifact hosts), and support HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment variables or a .env file for proxied environments. Notably, self-hosted runners require only outbound connectivity — they poll GitHub over HTTPS long-polling, so you never open an inbound firewall hole for them.
For hosted runners that must satisfy someone else's allowlist, GitHub publishes its IP ranges via the /meta API endpoint, and larger runners can be assigned static IP ranges — the two standard answers to "our vendor requires a fixed source IP."
⚠️ Exam Trap: Runner groups control scheduling access, not label matching. A job whose labels match a runner it isn't permitted to use does not fall back to another runner — it queues indefinitely with no error. "Job stuck in queued forever" almost always means either no runner carries all the requested labels, or a group policy excludes the repository.
Reflection Question: Self-hosted runners need no inbound firewall rules. Explain the communication pattern that makes this possible, and why it's a meaningful security advantage over a build agent that accepts inbound connections.