Self-hosted CI на Windows VPS чаще всего появляется не из любви к администрированию, а от безысходности: нужен MSBuild с нестандартными SDK, подпись артефактов через локальный HSM, сборка legacy-проекта, доступ к внутренним NuGet-репозиториям или деплой в контур, куда из публичных «облачных» раннеров просто не попасть.
В таких сценариях виртуальный сервер под управлением Windows становится удобной «рабочей лошадкой» для CI: выделили машину, поставили инструменты, подключили runner/agent и получили предсказуемую среду. А развернуть Windows VPS можно быстро, например, на VPS.house – как на площадке, где удобно поднимать Windows-серверы со статическим IPv4 и управлением из панели (их дата-центр в Москве тоже может быть полезен по задержкам, если команда и ресурсы рядом).
Дальше начинается взрослая часть: self-hosted CI даёт свободу и скорость, но переносит на вас ответственность за безопасность, обновления и «чистоту» сборок. Ниже разберём, как сделать это без лишней религии и без ощущения, что вы строите полноценную SIEM вокруг одного раннера.
Когда self-hosted CI реально оправдан Есть простой тест. Если хотя бы два пункта про вас, self-hosted обычно окупается:
Нужны специфические Windows-инструменты (старые версии SDK, COM-компоненты, драйверы, проприетарные компиляторы), которые на hosted-раннерах либо недоступны, либо требуют сложных плясок Нужен доступ к внутренним ресурсам: приватные репозитории, базы, файловые шары, артефакт-хранилища, стенды деплоя за firewall Сборки тяжёлые и выигрывают от «тёплых» кэшей: NuGet, npm, Gradle, Docker layers, большие репозитории. У self-hosted кэши и установленные зависимости живут между запусками, и это действительно ускоряет жизнь. Microsoft прямо отмечает, что на self-hosted агентах машинные кэши и конфигурация сохраняются от запуска к запуску и могут повышать скорость Вы хотите управлять «железом»: больше CPU/RAM, быстрый диск, гарантированные ресурсы, предсказуемая производительность Вы хотите контролировать стоимость: hosted-раннеры иногда упираются в параллелизм, лимиты, очереди и тарифы, а свою машину вы масштабируете под реальную загрузку GitHub Actions Runner и Azure DevOps Agent: что общего и что различается И GitHub Actions Runner, и Azure DevOps Agent решают одну задачу: на вашей машине крутится процесс (или сервис), который получает задания из облака и выполняет их в локальной среде.
Разница обычно в нюансах управления:
В GitHub вы привязываете runner на уровень репозитория, организации или enterprise, задаёте labels и runner groups, а задания направляете через runs-on. GitHub описывает, что у self-hosted runners есть уровни привязки и механика маршрутизации по labels и группам В Azure DevOps вы работаете через agent pools, capabilities и demands, а агент можно запускать интерактивно или как службу. Microsoft отдельно описывает режимы interactive vs service и рекомендуемые варианты запуска Главный риск self-hosted CI: выполнение недоверенного кода Self-hosted раннер по сути выполняет произвольный код из ваших workflow/pipeline. Это фундаментальное свойство CI, и его нельзя «отключить галочкой». Поэтому вопрос не «может ли код навредить», а «что будет, если вредоносный код всё-таки запустится».
GitHub очень прямолинеен: GitHub-hosted runners выполняют код в чистых эфемерных ВМ, а self-hosted не дают таких гарантий и могут быть персистентно скомпрометированы недоверенным workflow. GitHub также предупреждает, что self-hosted почти никогда не стоит использовать для публичных репозиториев, и призывает быть осторожными даже в private/internal, если есть возможность форков и PR.
Это не страшилка. Это правильная инженерная постановка задачи: CI – часть цепочки поставки (supply chain). Если раннер украдёт секреты или будет модифицировать артефакты, последствия выходят далеко за пределы «сломалась сборка».
Отсюда вытекает зрелая модель: self-hosted должен быть либо максимально изолированным, либо максимально одноразовым, либо и то и другое.
Три модели эксплуатации: «питомец», «скот» и «один job – одна жизнь» 1. Постоянный runner как «питомец»
Одна машина живёт месяцами, на ней стоят все инструменты, кэши, ключи, настройки. Это самый простой старт, но самый опасный по двум причинам:
накопление мусора и «дрейф» среды (сборки становятся нерепродуцируемыми) риск персистентной компрометации, если однажды запустится вредоносный код Если вы выбираете эту модель, вам нужны жёсткие правила гигиены: очистка workspace, контроль установок, обновления, мониторинг, ограничение доступа к секретам и сети.
2. Пересоздаваемый runner как «скот»
Вы описываете конфигурацию (PowerShell-скрипты, образ, шаги установки), и если что-то ломается или кажется подозрительным, вы не «лечите», а пересоздаёте машину. Это уже взрослее: среда воспроизводима, а атаки и дрейф лечатся переустановкой.
3. Эфемерный runner: один job – одна жизнь
GitHub поддерживает ephemeral self-hosted runners и даже рекомендует строить автоскейлинг именно на эфемерных раннерах, потому что они гарантированно получают не более одного job. GitHub описывает это в reference, а в changelog есть практическая деталь: runner можно конфигурировать как ephemeral через параметр --ephemeral, после job он автоматически снимается с регистрации.
Эта модель ближе всего к преимуществам hosted-раннеров, но её надо автоматизировать: доставку логов наружу, разворачивание/уничтожение, подготовку образа.
Базовая схема на Windows VPS: что открывать в сеть, а что нет Золотое правило: runner/agent сам инициирует исходящее соединение к GitHub/Azure DevOps по HTTPS. Это значит, что в большинстве случаев вам не нужны входящие порты для CI вообще.
Что обычно нужно:
исходящий доступ в интернет к GitHub/Azure DevOps и к вашим зависимостям (package registries, docker registries, nuget feeds) входящий доступ для администрирования сервера (и лучше не голый RDP в интернет, а через VPN/шлюз) Что обычно не нужно:
какие-либо входящие «для runner», если вы не делаете экзотические схемы Это важный психологический момент: self-hosted CI не обязан становиться «сервисом, торчащим наружу». В идеале он выглядит как обычный сервер, который сам ходит в облако и получает задания.
GitHub Actions Runner на Windows VPS: практический путь без лишней магии Шаг 1. Выберите уровень раннера и границы доступа
Repository-level runner подходит, когда нужен один проект и вы не хотите, чтобы другие репозитории могли запускать на нём workflow Organization-level runner удобен для нескольких репозиториев, но риски выше: компрометация может затронуть больше проектов. GitHub отдельно рекомендует использовать группы раннеров и ограничивать доступ репозиториев к группам, чтобы уменьшать «радиус поражения» Шаг 2. Придумайте метки (labels) под реальные типы задач
Плохая практика – один общий label self-hosted и всё. Хорошая – метки по смыслу: windows, msbuild, signing, docker, unity, gpu (если есть) и так далее. Так вы избегаете ситуации, когда тяжёлая сборка забивает раннер, а лёгкая простаивает в очереди.
Шаг 3. Запуск как сервис и логирование
Runner-агент на Windows обычно запускают как службу, чтобы он переживал ребуты. И сразу заложите привычку смотреть логи. GitHub описывает, что у runner есть диагностические логи в каталоге _diag, и их полезно сохранять для расследований проблем.
Шаг 4. Проверка сетевой доступности без гаданий
У GitHub runner есть режим проверки доступности нужных сервисов: config скрипт с параметром --check. Это экономит часы, когда что-то не коннектится из-за прокси или TLS. GitHub приводит пример для Windows (config.cmd --check ...) и поясняет требования к токену.
Шаг 5. Самое важное по безопасности: не давайте недоверенному коду бежать на раннер
Если у вас есть PR от внешних контрибьюторов или вообще любой сценарий, где workflow может запускаться на коде, который вы не контролируете, self-hosted раннер становится рискованным. GitHub прямо описывает этот класс рисков в рекомендациях secure use.
Практический вывод:
отдельные раннеры для «trusted» и «untrusted» задач запрет self-hosted для публичных репозиториев или жёсткие барьеры (approval, ограничения на who can run, отдельные группы) разделение workflow на две части, где недоверенный код запускается без секретов и с минимальными правами, а привилегированная часть работает только после проверок. Похожую логику рекомендует OpenSSF для GitHub workflows Шаг 6. Эфемерность как способ не помнить «а что осталось после прошлой сборки»
Если вы хотите максимально снизить риск утечки секретов между job и загрязнения среды, смотрите в сторону ephemeral runners. GitHub описывает поддержку эфемерных раннеров и практику автоскейлинга на них.
Даже если вы не делаете полноценный автоскейлинг, можно применить идею «один job – один запуск» локально: подготовили чистый образ, подняли VPS, отработали пайплайн, уничтожили.
Azure DevOps Agent на Windows VPS: чем он хорош и где ловушки Что Microsoft подчёркивает про self-hosted agents
Microsoft прямо фиксирует преимущества self-hosted: вы контролируете софт и зависимости, а кэши и конфигурация сохраняются между запусками, что может ускорять пайплайны.
И ещё одна практичная рекомендация: несмотря на возможность поставить несколько агентов на одну машину, Microsoft рекомендует один агент на машину, потому что несколько агентов могут ухудшать производительность и результаты пайплайнов.
Установка и требования без мифов
В официальной инструкции по Windows-агенту Microsoft перечисляет базовые prerequisites: Windows Server 2012+ (или соответствующие клиентские ОС), PowerShell 3.0+, и отдельно отмечает, что агент ставит свою версию .NET, то есть .NET не является prerequisite для установки агента.
Для сборки Visual Studio решений рекомендуются Visual Studio Build Tools и прочие зависимости по вашему стеку.
Interactive vs service: как запускать правильно
Microsoft описывает два режима: интерактивный и как сервис, и рекомендует сначала проверить в interactive, а в production перевести в один из service-режимов, чтобы агент жил стабильно и стартовал после перезагрузки. Для Windows также упоминаются service accounts вроде Network Service или Local Service как практичный вариант с ограниченными правами и без проблем с истечением пароля.
Практический перевод на человеческий:
«проверили руками, что пайплайны бегают» «поставили как службу» «запускаем от отдельной учётки с минимумом прав, а не от администратора просто потому, что так проще» Производительность: где self-hosted ускоряет жизнь, а где создаёт долг Быстро становится за счёт кэшей, но кэши требуют правил
Да, persistent кэши ускоряют. Но у кэшей есть тёмная сторона: они могут «подмешивать» неожиданные артефакты между проектами, если всё свалено в одну корзину.
Рабочие правила:
кэши делите по проектам или хотя бы по «трастовым доменам» чистите workspace после job, если раннер постоянный не храните секреты и приватные ключи в местах, доступных процессам сборки без необходимости фиксируйте версии зависимостей (lock-файлы), иначе кэш превращается в генератор «у меня локально собралось» Диск важнее, чем кажется
CI на Windows часто упирается не в CPU, а в IO: распаковка пакетов, сборка, линковка, генерация артефактов, работа антивируса. Поэтому быстрый SSD и правильная раскладка каталогов (workspace отдельно от системного, кэши отдельно) дают больше, чем бессмысленная погоня за лишними ядрами.
Инструменты на раннере должны быть воспроизводимыми
Если вы ставите SDK «руками через браузер», вы создаёте будущую неустойчивость. Лучше:
скрипт установки (PowerShell) зафиксированные версии проверка хэшей/подписей там, где это возможно регулярное пересоздание образа Это скучно, но это и есть инженерия.
Релизы и деплой: зачем self-hosted нужен даже тем, у кого сборки и так работают Самая частая причина «переезда на self-hosted» – не сборка, а релиз. Деплой в закрытый контур, доступ к внутреннему API, выкладка на сервера без публичного доступа, миграции БД под контролем, интеграция с внутренними системами.
Здесь Windows VPS удобен как «точка, которая видит и облако, и ваш контур». Со статическим IPv4 проще делать allowlist на стороне корпоративных сервисов. А если у вас несколько серверов, приватные сети между ними упрощают схему «runner отдельно, окружения отдельно».
Главное правило релизов: раннер, который деплоит, не должен быть тем же раннером, на котором вы исполняете недоверенный код. Разделяйте сборку и деплой по уровням доверия.
Минимальный чек-лист безопасности для self-hosted CI на Windows Не запускайте workflow на self-hosted раннере для публичных репозиториев и недоверенных PR. Это прямое предупреждение GitHub Разделяйте раннеры по группам и ограничивайте доступ репозиториев к группам, чтобы уменьшать последствия компрометации Минимизируйте секреты на раннере. Секрет, который не лежит на машине, нельзя украсть с машины Держите ОС и инструменты обновлёнными. Self-hosted даёт контроль, но вы отвечаете за патчи. GitHub прямо напоминает, что вы отвечаете за обновления ОС и софта на self-hosted Делайте раннер одноразовым там, где это реально. Эфемерные раннеры снижают риск загрязнения и утечек между job Ограничьте сеть: раннеру нужны исходящие соединения, но не нужен «вход в мир» Логи и диагностика: сохраняйте логи runner/agent отдельно, особенно если делаете одноразовые раннеры. GitHub отдельно отмечает важность внешнего хранения логов для эфемерных решений. Как начать с минимальными рисками: практичный план на один вечер Шаг 1. Поднимите отдельный Windows VPS под CI (не на том же сервере, где живёт прод). Если нужно быстро протестировать, удобна аренда на короткий срок. Например, взять такой сервер можно на VPS.house и за вечер понять, сколько ресурсов реально требуется вашим сборкам.
Шаг 2. Поставьте инструменты сборки в воспроизводимом виде (скрипт, список версий).
Шаг 3. Подключите GitHub Actions Runner или Azure DevOps Agent сначала в интерактивном режиме, убедитесь, что job выполняются, затем переведите в службу.
Шаг 4. Включите «гигиену» workspace: очистка, контроль кэшей, понятные каталоги.
Шаг 5. Разделите раннеры по уровням доверия и не допускайте недоверенный код к машинам, которые видят секреты и прод.
Шаг 6. Через неделю эксплуатации решите, нужна ли эфемерность: если да, автоматизируйте пересоздание и вывоз логов.
В сухом остатке Self-hosted CI на Windows VPS – это не «альтернатива облаку ради экономии», а инструмент управления средой. Он особенно хорош там, где Windows-стек, закрытые сети и тяжёлые сборки. Но он требует взрослого отношения к безопасности и воспроизводимости, потому что CI выполняет код, а значит может стать точкой входа в цепочку поставки.
Если сделать всё аккуратно – отдельная машина, ограниченный доступ, разделение доверия, разумные кэши и регулярное обновление – вы получаете быстрые сборки, предсказуемые релизы и контроль над тем, что именно происходит в процессе доставки.