Пока проект небольшой, облачных машин GitHub хватает с запасом. Потом начинаются сюрпризы: бесплатные минуты заканчиваются на середине месяца, тяжёлая сборка еле ползёт, а до тестового стенда в закрытой сети задание из облака просто не дотягивается. Стандартный Linux-раннер для приватных репозиториев получает 2 vCPU и 7 ГБ памяти. Для линтеров и юнит-тестов этого достаточно, но монорепозиторий или образ с большими зависимостями уже упираются в потолок. В таких случаях вопрос о собственной машине для GitHub Actions возникает сам собой.
Решение лежит на поверхности: взять обычный виртуальный сервер и подключить его к репозиторию или организации. Ресурсы вы выбираете сами, платите по понятному тарифу, а доступ к внутренним сервисам настраивается без белых списков адресов GitHub. Ниже разберём, как устроен self-hosted runner, как пошагово развернуть раннер на VPS, чем он лучше и хуже облачных машин и какие ошибки встречаются чаще всего.
Что такое self-hosted раннер и при чём здесь VPS
Runner — это небольшое приложение, которое получает задания из workflow и выполняет их на той машине, где запущено. GitHub предлагает два варианта. В первом раннерами управляет сам GitHub: под каждое задание создаётся чистая виртуальная машина, после завершения она удаляется. Во втором приложение ставите вы, на своём оборудовании. Проще говоря, в одном случае вы арендуете чужой компьютер на время задачи, в другом работаете на собственном и держите его включённым столько, сколько нужно.
Роль этой машины может играть сервер в офисе, виртуалка в частном облаке или VPS у хостинг-провайдера. Последний вариант самый гибкий. Сервер создаётся за несколько минут, масштабируется сменой тарифа, а о питании, охлаждении и сетевом канале думать не приходится. Что немаловажно, для CI редко требуется выделенное железо: нагрузка идёт волнами, и виртуальный сервер с достаточным числом ядер её переносит. Единственное условие, которое GitHub предъявляет к машине, — исходящий доступ в интернет и поддерживаемая операционная система. С этим справится любой современный дистрибутив Linux.
Прежде чем строить инфраструктуру, стоит сравнить оба подхода по основным параметрам. На первый взгляд кажется, что собственный сервер выигрывает везде, но у него есть и обратная сторона.
| Параметр | Раннер GitHub | Раннер на VPS |
|---|---|---|
| Ресурсы | Фиксированные типы машин, для приватных репозиториев стандартный Linux — 2 vCPU и 7 ГБ RAM | Число ядер, память и диск выбираете сами |
| Окружение | Чистая машина на каждое задание | Постоянное: кэши и образы сохраняются. Чистое окружение даёт режим ephemeral |
| Стоимость | Бесплатный лимит минут по тарифу, дальше оплата за каждую минуту | Оплата VPS по тарифу независимо от числа минут |
| Доступ к внутренней сети | Только через публичные адреса | Раннер стоит рядом с вашими сервисами |
| Обслуживание | На стороне GitHub | Обновления ОС, мониторинг и очистка диска на вас |
| Безопасность | Изоляция из коробки | Зависит от того, как вы настроите сервер |
Теперь ясно, где граница. Если задач мало и они лёгкие, облачные машины GitHub проще и надёжнее. Если сборок много, они тяжёлые или нужен доступ к закрытым ресурсам, собственный self-hosted runner на виртуальном сервере начинает окупаться.
Как это работает
Схема проще, чем кажется. После регистрации приложение само обращается к GitHub по HTTPS и держит длинное соединение, ожидая задание (long polling). Когда в репозитории срабатывает workflow с подходящим значением runs-on, GitHub отдаёт задание свободному раннеру. Тот забирает шаги, скачивает нужные actions, выполняет их и отправляет обратно логи и статус. Входящие подключения не нужны вообще. Открывать порты на VPS снаружи не придётся: достаточно исходящего доступа к github.com и нескольким связанным доменам.
Выбор исполнителя строится на метках (labels). Каждый раннер получает стандартные self-hosted, linux и x64, к ним вы добавляете собственные, например vps или build. В workflow перечисляете нужные метки, и задание уходит только на ту машину, где есть все из списка. Через метки же организуют разделение труда: тяжёлую сборку отправляют на мощный сервер, а деплой на тот, что находится внутри закрытого контура.
Стоит отметить одно ограничение: один экземпляр приложения выполняет одно задание за раз. Для параллельных сборок на одном VPS запускают несколько экземпляров, каждый в своём каталоге и с уникальным именем. Либо берут сервер побольше и делят его ресурсы между раннерами.
Пошаговая настройка раннера на VPS
Покажем процесс на Ubuntu 22.04 или 24.04. Остальные популярные дистрибутивы настраиваются похоже. Для старта хватит 2 vCPU, 4 ГБ RAM и 40 ГБ диска, а под сборку Docker-образов и тяжёлые тесты закладывайте больше. Итак, чтобы получить рабочий раннер на VPS, пройдите семь шагов.
Шаг 1. Подготовьте сервер
Создайте виртуальный сервер, например VPS Serverspace, подключитесь по SSH и обновите систему:
Затем заведите отдельного пользователя, под которым будет работать раннер. Права администратора ему не нужны: чем меньше полномочий, тем меньше ущерба при ошибке в workflow.
Шаг 2. Закройте лишние порты
Раннеру достаточно исходящих соединений, поэтому снаружи оставляем только SSH:
Если исходящий трафик на сервере ограничен, разрешите доступ к github.com, api.github.com и доменам *.actions.githubusercontent.com. Без них раннер не получит задания.
Шаг 3. Установите зависимости и Docker
Большинству workflow нужны git, curl и jq:
Если сборки идут в контейнерах, поставьте Docker и добавьте пользователя runner в одноимённую группу:
Важно помнить: членство в группе docker по сути равно правам root на хосте. Для приватной команды это приемлемо, для кода из непроверенных источников нет.
Шаг 4. Скачайте приложение runner
В настройках репозитория или организации откройте раздел Settings → Actions → Runners, нажмите кнопку создания нового раннера и выберите Linux с подходящей архитектурой. GitHub покажет актуальные команды: ссылку на нужную версию и временный токен. Переключитесь на пользователя runner и повторите их:
Вместо ВЕРСИИ подставьте номер, который показывает страница GitHub. Не копируйте его из старых инструкций: слишком старые версии приложения сервис перестаёт принимать.
Шаг 5. Зарегистрируйте раннер
Регистрация выполняется одной командой:
Токен действует около часа и подходит для одной регистрации. Если команда ругается на него, просто создайте новый на странице GitHub. Основные параметры сведены в таблицу.
| Параметр | Что делает |
|---|---|
| --url | Адрес репозитория или организации, к которым подключается раннер |
| --token | Временный токен регистрации со страницы настроек |
| --name | Имя в списке раннеров; у каждого экземпляра оно уникально |
| --labels | Дополнительные метки для выбора раннера в runs-on |
| --runnergroup | Группа раннеров в организации, ограничивает набор репозиториев |
| --work | Каталог для рабочих файлов заданий |
| --ephemeral | Раннер выполняет одно задание и удаляет себя из списка |
| --replace | Заменяет раннер с таким же именем при повторной регистрации |
| --unattended | Отключает интерактивные вопросы, удобно для скриптов |
Шаг 6. Запустите раннер как службу
Для проверки достаточно команды ./run.sh, в интерфейсе GitHub раннер сразу получит статус Idle. Но постоянная работа требует systemd-службы. Выйдите из учётной записи runner, получите права root через sudo -i, перейдите в каталог раннера и выполните:
Теперь приложение стартует вместе с сервером и перезапускается после сбоев. Если раннер больше не нужен, снимите его с регистрации командой ./config.sh remove --token ТОКЕН.
Шаг 7. Подключите раннер в workflow
Остаётся указать в файле workflow нужные метки:
Сделайте коммит и откройте вкладку Actions. Задание уйдёт на ваш сервер, а в логах появится имя машины. На этом базовая настройка завершена. Дальше начинается эксплуатация, и здесь пригодится прикидка по ресурсам.
| Тип нагрузки | Ориентир по ресурсам |
|---|---|
| Линтеры, юнит-тесты, небольшие проекты | 2 vCPU, 4 ГБ RAM, 40 ГБ диска |
| Сборка Docker-образов, интеграционные тесты | 4 vCPU, 8 ГБ RAM, 80 ГБ и больше |
| Компиляция крупных проектов, несколько раннеров на одной машине | 8 vCPU, 16 ГБ RAM и выше, быстрый NVMe |
Это лишь отправные точки. Реальные цифры покажет мониторинг: понаблюдайте за загрузкой процессора и памяти в первые недели и подберите тариф под фактическую нагрузку.
Плюсы и минусы
Начнём с сильных сторон.
- Экономия при плотной нагрузке. Вы платите за сервер, а не за каждую минуту. Допустим, команда расходует 10 000 минут в месяц сверх бесплатного лимита. По ставке около $0,006 за минуту двухъядерного Linux-раннера GitHub это порядка $60. Если VPS нужного размера стоит заметно меньше, выгода очевидна.
- Контроль над ресурсами. Нужно больше памяти или быстрый диск, достаточно сменить тариф.
- Кэши и готовые инструменты. Слои Docker, зависимости и компиляторы остаются на машине, поэтому повторные сборки идут заметно быстрее.
- Доступ к закрытой инфраструктуре. Раннер видит внутренние базы, реестры и серверы без пробросов наружу.
- Предсказуемый старт заданий. Пока машина свободна, задание начинается почти сразу.
Теперь о цене этих преимуществ.
- Обслуживание ложится на вас: обновления системы, мониторинг, перезапуски, чистка диска.
- Безопасность требует внимания, о чём подробнее в следующем разделе.
- Масштабирование идёт вручную, пока вы не соберёте автоматику вокруг ephemeral-раннеров.
- Сервер оплачивается и тогда, когда сборок нет. Ночью и в выходные он простаивает.
- Постоянное окружение накапливает мусор: старые образы, кэши и временные файлы незаметно съедают диск.
Ограничения и риски
Главный риск связан с публичными репозиториями. GitHub не рекомендует подключать к ним self-hosted runner: любой человек может открыть pull request из форка, и его код выполнится на вашем сервере. Для открытых проектов используйте облачные раннеры. Если без собственной машины всё же не обойтись, ограничьте запуск workflow из форков и держите такой сервер в изолированной сети без доступа к чему-либо ценному.
Второй момент касается секретов и доверия между задачами. Раннер получает доступ к секретам, которые передаёт workflow, а на постоянной машине остаются следы прошлых заданий: файлы в рабочем каталоге, переменные окружения, кэши, учётные данные Docker. Один и тот же сервер для сборки непроверенных pull request и для деплоя в продакшен — плохая идея. Разделяйте их по разным VPS, группам раннеров и меткам. В организации доступ к раннерам ограничивают через runner groups: так конкретный сервер доступен только нужным репозиториям.
Не забывайте и про права. Пользователь runner без sudo и без лишних ключей SSH на диске ограничивает последствия любой ошибки. Группа docker, как уже говорилось, открывает путь к root, поэтому там, где контейнеры нужны не всем, лучше запускать их в rootless-режиме или использовать отдельный сервер.
Отдельно стоит упомянуть финансовую сторону. В декабре 2025 года GitHub анонсировал плату в $0,002 за минуту работы self-hosted-раннеров в приватных репозиториях, а затем отложил это решение, чтобы пересмотреть подход. Подчёркивалось, что сам вопрос остаётся открытым. Для вас это значит одно: условия биллинга GitHub Actions могут измениться, и перед расчётом экономии лучше свериться с актуальной документацией по тарифам.
Наконец, надёжность. Если VPS недоступен, задания зависают в очереди и ждут. Для критичных пайплайнов держите два раннера с одинаковыми метками, и при сбое одного работа не остановится.
Практические сценарии
Сценарий 1. Сборка Docker-образов с кэшем слоёв
Классическая причина перейти на собственный сервер. В облаке каждая сборка начинается с нуля, и базовые слои приходится скачивать заново. На постоянном раннере они лежат на диске, и сборка занимает минуты вместо четверти часа. Достаточно регулярно чистить неиспользуемые образы командой docker system prune, чтобы диск не заполнился.
Сценарий 2. Деплой во внутренний контур
У вас есть тестовые и боевые серверы, доступные только из частной сети. Открывать SSH для всех адресов GitHub небезопасно, да и список этих адресов меняется. Раннер на VPS, расположенном в той же сети, решает вопрос: он получает задание по исходящему соединению и выполняет деплой по внутренним адресам. Для такого сценария заведите отдельный раннер с меткой deploy и подключите его только к тем репозиториям, которым вы доверяете.
Сценарий 3. Тяжёлые тесты с зависимостями
Интеграционные тесты часто требуют PostgreSQL, Redis или очередь сообщений. На собственной машине их поднимают через docker compose за несколько секунд и не платят за минуты, пока идёт долгий прогон. Тем же способом решаются задачи с большими датасетами, браузерными тестами или компиляцией на C++ и Rust, где нужны много ядер и памяти.
Сценарий 4. Одноразовые раннеры для изоляции
Если нужна чистота облачных машин и контроль собственных, используйте режим ephemeral. Раннер запускается с параметром --ephemeral, выполняет одно задание и удаляется, а скрипт или systemd-служба тут же поднимает новый. Каждое задание получает свежее окружение, следы прошлых запусков не накапливаются. Для автоматического масштабирования в Kubernetes используют Actions Runner Controller, но для небольшой команды хватит и скрипта на одном VPS.
Типичные ошибки
Большинство проблем повторяется от команды к команде. Часть из них удобно свести в таблицу «симптом, причина, решение».
| Симптом | Причина | Решение |
|---|---|---|
| Раннер в статусе Offline | Остановилась служба или закрыт исходящий доступ | Проверить ./svc.sh status и доступность github.com с сервера |
| Задание висит в Queued | Нет раннера со всеми метками из runs-on | Сверить метки в workflow и в настройках раннера |
| Permission denied при вызове docker | Пользователь не входит в группу docker или служба не перезапущена | Добавить в группу и перезапустить службу раннера |
| No space left on device | Накопились образы, кэши и рабочие каталоги | Настроить регулярную очистку, увеличить диск |
| Ошибка токена при регистрации | Токен живёт около часа и одноразовый | Сгенерировать новый на странице GitHub |
| Задания падают без понятной причины | Не хватает оперативной памяти, процесс убит системой | Посмотреть журнал ядра, добавить swap или выбрать тариф побольше |
Есть и ошибки организационного характера. Раннер запускают от root, потому что «так быстрее настроить». Подключают сервер к публичному репозиторию и забывают про форки. Делают одну машину для всего: от проверки сторонних pull request до выкладки в прод. Не ставят мониторинг и узнают о проблеме от разработчиков, когда сборки уже стоят час. Ещё один частый промах: не планируют обновление системы, и через полгода сервер работает на устаревших пакетах с известными уязвимостями. Каждую из этих ошибок проще предотвратить на старте, чем разбирать последствия.
Итоги
Собственный сервер для CI окупается там, где много сборок, тяжёлые задачи или нужен доступ во внутреннюю сеть. Раннер на VPS настраивается за час: отдельный пользователь, закрытый брандмауэр, приложение runner, служба systemd и метки в workflow. Дальше всё упирается в дисциплину: ограничивать репозитории, не мешать доверенный и недоверенный код, чистить диск и следить за ресурсами. Если сомневаетесь, начните с одного небольшого сервера для самой дорогой по минутам задачи и посмотрите на результат за месяц. Больше практических материалов по серверам и DevOps собрано в блоге Serverspace.
FAQ
Нужен ли белый IP-адрес или открытые порты для раннера?
Нет. Раннер сам подключается к GitHub по исходящему HTTPS, поэтому входящие порты на сервере открывать не требуется. Достаточно, чтобы VPS мог обращаться к github.com и связанным доменам.
Сколько раннеров можно запустить на одном VPS?
Столько, сколько выдержит сервер по процессору, памяти и диску. Каждый экземпляр ставят в отдельный каталог и регистрируют под уникальным именем. На практике для начала хватает двух-трёх раннеров на машине с 4–8 ядрами.
Можно ли использовать собственный раннер с публичным репозиторием?
Технически можно, но GitHub этого не рекомендует: код из pull request форка выполнится на вашем сервере. Для открытых проектов лучше брать облачные раннеры, а собственные оставить для приватных репозиториев.
Чем ephemeral-раннер отличается от обычного?
Обычный раннер живёт постоянно и выполняет задания одно за другим, сохраняя файлы и кэши. Ephemeral принимает одно задание и сразу удаляется из списка. Это даёт чистое окружение на каждый запуск, но требует автоматики, которая создаёт новые раннеры.
Нужно ли обновлять приложение runner вручную?
Как правило, нет: приложение обновляется само, и этот механизм можно отключить параметром при регистрации. А вот систему на VPS обновляете вы. Включите автоматические обновления безопасности и периодически перезагружайте сервер.
Будет ли GitHub брать плату за использование собственных раннеров?
В декабре 2025 года GitHub анонсировал плату в $0,002 за минуту для приватных репозиториев, а затем отложил её введение для пересмотра. Итоговые условия лучше проверять в официальной документации по тарифам перед тем, как считать экономию.