Po co w ogóle myśleć o self hosted runnerach?
Cel: kontrola, koszty i specyficzne potrzeby
Self hosted runner to własna maszyna (fizyczna, wirtualna lub kontener), na której uruchamiają się zadania z pipeline’u (build, test, deploy), zamiast standardowej infrastruktury dostawcy typu GitHub, GitLab czy Azure DevOps. Sam runner to tylko agent – usługa, która łączy się z platformą CICD i wykonuje to, co ona zleci.
Powód, dla którego zespoły w ogóle wchodzą w temat self hosted runnerów, jest zwykle banalny: standardowe, „chmurowe” runnery przestają wystarczać. Albo zjadają zbyt dużo minut rozliczeniowych, albo nie mają dostępu do środowisk wewnętrznych, albo nie radzą sobie z ciężkimi buildami (GPU, dużo RAM, specjalne pakiety systemowe).
Z drugiej strony, własny runner to także odpowiedzialność: bezpieczeństwo, utrzymanie, aktualizacje, monitoring. Źle zaprojektowany self hosted runner potrafi stać się wygodnym mostem dla atakującego w głąb sieci firmowej. Stąd kluczowe pytania: czy naprawdę jest sens go stawiać oraz jak go zabezpieczyć, żeby nie palić budżetu i nie otworzyć nowych wektorów ataku.
Czym jest self hosted runner i gdzie pojawia się w CICD
Krótkie porównanie z runnerami SaaS
Standardowy model CICD w usługach takich jak GitHub Actions, GitLab CI czy Azure DevOps opiera się o runnery zarządzane przez dostawcę. Oznacza to, że po triggerze pipeline’u dostawca tworzy tymczasową maszynę (często kontener lub VM), uruchamia zadania, a po zakończeniu ją usuwa. Aktualizacje systemu, bezpieczeństwo na poziomie hosta i skalowanie są jego problemem. Ty płacisz za minuty lub za pakiet.
Self hosted runner działa odwrotnie: to Twoja maszyna łączy się z platformą CICD. Platforma widzi ją jako dostępnego agenta i wysyła do niej joby. Cała odpowiedzialność za:
- system operacyjny, jego aktualizacje i konfigurację,
- dostępną moc obliczeniową (CPU, RAM, GPU),
- dostęp sieciowy do innych systemów,
- bezpieczeństwo, segmentację, logowanie,
spoczywa na zespole, który runnera utrzymuje.
Różnica z perspektywy użytkownika pipeline’u może być minimalna: w GitHub Actions zmieniasz runs-on: ubuntu-latest na runs-on: [self-hosted, linux, build], w GitLab dodajesz odpowiednie tagi runnera. Jednak z perspektywy operacyjnej to zupełnie inny model odpowiedzialności.
Rola runnera w typowym pipeline CICD
W typowym przepływie CICD runner pojawia się praktycznie na każdym etapie:
- Build – kompilacja kodu, pobranie zależności, budowa obrazów Docker, paczek NuGet, Maven, npm itp.
- Test – testy jednostkowe, integracyjne, e2e, testy wydajnościowe czy bezpieczeństwa.
- Packaging i artefakty – tworzenie paczek, bundli front-end, upload do registry (Docker Registry, Artifactory, Nexus).
- Deploy – wdrażanie na serwery, do Kubernetesa, na platformy PaaS, aktualizacja funkcji serverless.
Na każdym z tych kroków runner:
- odczytuje kod z repozytorium,
- pobiera i używa sekretów (tokeny, klucze SSH, hasła),
- ma dostęp do zewnętrznych usług (bazy danych, API, chmura),
- generuje logi i artefakty, które są później przechowywane i analizowane.
Dlatego self hosted runner staje się elementem krytycznym: ma w ręku zarówno kod, jak i klucze do królestwa (produkcyjna infrastruktura, rejestry Docker, systemy wewnętrzne). Jeżeli ktoś przejmie kontrolę nad runnerem, ma zwykle znacznie więcej niż tylko dostęp do jednego repozytorium.
Formy, w jakich może działać self hosted runner
Self hosted runner nie musi być tylko jedną fizyczną maszyną w serwerowni. Może pracować w różnych formach:
- Serwer fizyczny (bare-metal) – dobre rozwiązanie przy obciążeniach wymagających stabilnej wydajności lub specyficznego sprzętu (GPU, karty sieciowe, dużo RAM). Minusem jest brak elastycznego skalowania.
- Maszyna wirtualna (on-premise lub w chmurze) – najbardziej typowy scenariusz. Łatwo ją powielić, snapshotować, podmienić obraz bazowy. Nadaje się dla mniejszych i średnich zespołów.
- Kontener (np. Docker, Kubernetes) – runner uruchomiony jako kontener, a właściwe joby odpalane w innych kontenerach. Ułatwia izolację i replikację, ale wymaga znajomości orkiestracji.
- Autoskalujące runnery w chmurze – np. GitLab autoscaling runners, GitHub Actions + autoscaling group w AWS. Agent sam odpala nowe instancje VM pod obciążeniem i je gasi, gdy pipeline’y się kończą.
Sama platforma CICD (GitHub, GitLab, Azure DevOps) łączy się z runnerem przez protokół HTTPS/WebSocket, używając zarejestrowanego tokena. Wymiana danych obejmuje:
- skrypty jobów (kroki pipeline’u),
- artefakty (upload/download),
- logi przesyłane z powrotem do UI,
- czasem cache dependency (np. cache npm, Maven) między pipeline’ami.
Ten kanał komunikacyjny musi być traktowany jako punkt zaufania: kompromitacja tokena runnera oznacza możliwość podłączenia fałszywego agenta, który będzie wykonywał joby lub wstrzykiwał dane do pipeline’ów.

Kiedy self hosted runner ma sens, a kiedy lepiej zostać przy SaaS
Decyzja oczami budżetowego pragmatyka
Najrozsądniej zacząć od prostego zestawu pytań. Zamiast iść w self hosted „bo wszyscy tak robią”, lepiej odpowiedzieć sobie szczerze:
- Czy obecne runnery SaaS faktycznie blokują pracę zespołu?
- Czy ktoś w zespole realnie ma czas i kompetencje, żeby utrzymywać infrastrukturę runnerów?
- Czy mamy jasny powód biznesowy: koszty, compliance, wydajność, dostęp do sieci wewnętrznej?
- Czy potrafimy policzyć choćby orientacyjnie koszt godzinowy inżyniera vs koszt minut runnerów SaaS?
Jeśli na większość z tych pytań odpowiedź brzmi „nie” albo „nie wiem”, lepiej zostać przy runnerach dostawcy i skupić się na optymalizacji pipeline’ów. Self hosted runner ma sens wtedy, gdy rozwiązuje konkretny, powtarzalny problem, którego inaczej nie da się zamknąć w rozsądnym budżecie czasu i pieniędzy.
Sytuacje, w których runner SaaS już nie wystarcza
Kilka typowych scenariuszy, gdzie GitHub Actions czy GitLab Shared Runners zaczynają być zbyt ciasne:
- Wymagający build – np. trenowanie modeli ML, kompilacja dużych projektów C++/Rust, testy wydajnościowe. Potrzeba GPU, dużej ilości RAM, specyficznego kernela lub dedykowanych bibliotek systemowych.
- Specyficzne środowisko systemowe – niestandardowy system (np. specyficzna dystrybucja Linuksa), stare biblioteki, dziwne sterowniki. Przestawianie tego w standardowym runnerze SaaS jest trudne lub niemożliwe.
- Duże artefakty i transfer danych – obrazy Docker o dużej wadze, ogromne zestawy testowe, intensywne korzystanie z cache. Płacisz nie tylko za minuty, ale także „straty” na transferach i opóźnienia pipeline’ów.
- Wysoka intensywność pipeline’ów – wiele repozytoriów z bardzo częstymi commitami i PR-ami. Minuty runnerów SaaS zaczynają szybko się sumować do poziomu zbliżonego do kosztu własnych maszyn.
Do tego dochodzi aspekt czysto praktyczny: czas oczekiwania w kolejce na runnerach współdzielonych. Jeżeli pipeline’y zaczynają się „ustawiać” i deweloperzy czekają po kilka–kilkanaście minut, produkt traci tempo. Własny pool runnerów może być tańszy niż utrata produktywności zespołu.
Compliance, sieć wewnętrzna i brak zgody na kod w chmurze publicznej
W wielu branżach (finanse, medycyna, administracja publiczna) nie chodzi o wygodę, tylko o wymogi formalne:
- Polityka zabrania wykonywania kodu na infrastrukturze poza kontrolą organizacji.
- Testy integracyjne muszą sięgać do wewnętrznych baz danych, systemów legacy lub wewnętrznych API dostępnych tylko w VPN/VPC.
- Wymagana jest ścisła kontrola nad przechowywaniem logów i artefaktów, a to łatwiej uzyskać w pełni kontrolowanym środowisku.
Self hosted runner on-premise lub w prywatnej chmurze z kontrolowanym dostępem do sieci wewnętrznej bywa jedyną opcją, aby:
- spełnić wymogi audytów,
- nie narażać się na zarzut, że kod aplikacji lub dane testowe „wychodzą” poza organizację,
- nie wprowadzać skomplikowanych mostów VPN do chmury publicznej.
W takich przypadkach, nawet jeżeli self hosted generuje dodatkowy koszt utrzymania, ryzyko regulacyjne bywa znacznie droższe.
Koszty: minuty runnerów vs koszt maszyn i utrzymania
Żeby decyzja miała sens, warto zestawić liczby, choćby w przybliżeniu:
- ile minut miesięcznie realnie zużywają pipeline’y,
- jaka jest stawka za 1000 minut runnerów SaaS w danym planie,
- ile kosztowałby serwer/VM o podobnych parametrach przy ciągłej pracy,
- ile czasu inżyniera miesięcznie pochłonie utrzymanie runnerów (updates, incydenty, zmiany).
| Aspekt | Runnery SaaS | Self hosted runnery |
|---|---|---|
| Model kosztów | Minuty / pakiety usage | Maszyny + czas inżynierów |
| Skalowanie | Automatyczne, bezobsługowe | Trzeba zaprojektować (ręcznie lub autoscaling) |
| Specjalne zasoby (GPU itp.) | Ograniczone lub bardzo drogie | Pełna dowolność konfiguracji sprzętu |
| Dostęp do sieci wewnętrznej | Trudny, wymaga mostów/VPN | Naturalny, jeśli runner stoi w tej samej sieci |
| Bezpieczeństwo hosta | Po stronie dostawcy | Po stronie Twojego zespołu |
| Czas wdrożenia | Bardzo krótki (gotowe środowisko) | Od kilku dni do kilku tygodni przy złożonym setupie |
Dobra praktyka: jeżeli koszt minut runnerów SaaS nie przekracza kilku procent budżetu IT, a zespół nie narzeka na kolejki i ograniczenia techniczne, self hosted często jest przedwczesną optymalizacją. Lepiej wtedy zainwestować w poprawę pipeline’ów (cache, równoległość, mniej zbędnych jobów).
Proste drzewko decyzyjne – kiedy wejść w self hosted
Praktyczny zestaw pytań na start:
- Czy pipeline’y regularnie utykają przez brak zasobów runnerów SaaS?
- Czy budżet na minuty runnerów SaaS realnie boli kogoś poza zespołem DevOps?
- Czy wymagane jest połączenie z siecią wewnętrzną lub systemami legacy, do których trudno zbudować bezpieczny dostęp zewnętrzny?
- Czy w zespole jest co najmniej jedna osoba, która:
- zna się na systemach, sieciach i bezpieczeństwie,
- ma formalnie oddelegowany czas na utrzymanie runnerów?
- Czy macie jasno opisane minimalne wymagania bezpieczeństwa (aktualizacje, backupy, dostęp sieciowy)?
Jeśli odpowiedź „tak” pada przy większości pytań, a koszt minut SaaS jest już zauważalny, przejście na self hosted – choćby w części pipeline’ów – zaczyna mieć sens. W innym przypadku rozsądniej zostać przy SaaS i zamiast tego zredukować czas buildów i testów.
Typy self hosted runnerów i modele wdrożeń (on-premise, chmura, hybryda)
Najprostszy scenariusz: pojedyncza maszyna
Wiele zespołów startuje od najprostszego możliwego modelu: jedna VM lub jeden serwer fizyczny z zainstalowanym agentem runnera. Technicznie działa to bardzo szybko:
- stawiasz maszynę (np. VM w chmurze lub serwer on-premise),
- instalujesz system (np. Ubuntu LTS),
Pool małych maszyn zamiast jednego „potwora”
Pojedynczy serwer kusi prostotą, ale szybko wychodzą jego ograniczenia: awaria = brak CI, jeden duży build blokuje resztę. Zdrowszym podejściem jest kilka mniejszych runnerów w poolu, nawet jeśli łączna moc CPU będzie podobna.
- Przy awarii jednej maszyny pipeline’y zwalniają, ale nie stają całkowicie.
- Łatwiej rotować hosty (np. przebudować jedną VM od zera, podczas gdy pozostałe obsługują ruch).
- Można przydzielać konkretne typy jobów do konkretnych runnerów przez
labels/tags(np.gpu,large-memory).
Dla zespołów, które dopiero wychodzą z SaaS, często wystarcza 2–4 VM w jednej strefie dostępności, spięte prostym autoscalingiem lub manualnym włączaniem/wyłączaniem według obciążenia.
Samoregenerujące się runnery: krótkie życie, mało śmieci
Kolejny krok to model, w którym runner jest tworzony „na chwilę”, wykonuje kilka jobów i jest niszczony. Może to być:
- VM z autoscalera (AWS ASG, Google MIG, Azure VMSS),
- kontener w Kubernetesie (np. GitLab Runner w trybie
kubernetes), - maszyna zbudowana przez narzędzie typu Packer + Terraform i wyłączana po wykorzystaniu.
Z perspektywy kosztów ten model ma kilka plusów:
- minimalizuje „drift” konfiguracji – każda instancja startuje z tego samego obrazu (template),
- zmniejsza ryzyko, że ktoś ręcznie „doklika” coś na serwerze i zapomni o tym,
- daje możliwość wyłączania środowiska w godzinach nocnych/poza szczytem bez długich ręcznych operacji.
Dla mniejszych zespołów nie trzeba od razu budować pełnego autoscalera. Czasem wystarczy:
- jeden złoty obraz (AMI, image, snapshot),
- prosty skrypt/Makefile do tworzenia i usuwania runnerów na żądanie,
- kilka metryk w Prometheusie lub CloudWatch, by obserwować, czy się nie dusi.
On‑premise: gdy sieć wewnętrzna jest królem
Przy środowiskach on‑premise głównym motywem są zwykle systemy legacy i compliance. Architektura runnera często wygląda tak:
- kilka VM lub hostów bare‑metal w tym samym VLAN/VXLAN co środowiska testowe i staging,
- dostęp do repozytoriów przez VPN lub dedykowany tunel (np. IPsec, WireGuard, prywatny peering do chmury),
- lokalny rejestr Docker/OCI oraz serwer artefaktów (np. Nexus, Artifactory, GitLab Package Registry).
On‑premise ma dużo pokus: „wszystko pod ręką”, „pełna kontrola”. Z drugiej strony:
- trzeba zadbać o fizyczną infrastrukturę (prąd, chłodzenie, zapasowe zasilanie),
- często brakuje prostego autoscaling’u – dokładanie mocy to zamawianie kolejnych serwerów,
- łatwo mieszać ruch produkcyjny z CI, co w razie błędu może odbić się na aplikacjach business‑critical.
Rozsądny kompromis: wydzielona odseparowana podsieć z hostami runnerów, spięta z resztą data center przez firewalle i listy ACL, ale bez dostępu „wszędzie i na wszystko”.
Chmura publiczna: elastyczność na minuty
W chmurze publicznej najprostszy i zwykle najtańszy start to:
- jedna mała VPC/VNet z subnetem dla runnerów,
- prosty autoscaling grupy VM (lub node pool w Kubernetesie),
- dostęp do repo (GitHub/GitLab) po HTTPS, bez dodatkowych tuneli,
- private endpointy do S3/Blob/Cloud Storage dla artefaktów.
Główne koszty to:
- czas działania VM (zwykle tańszy niż minuty SaaS przy dużym obciążeniu),
- przechowywanie artefaktów i cache (storage + egress między regionami, jeśli nieuważnie dobrano lokalizacje),
- czas inżyniera na utrzymanie obrazu bazowego i aktualizacji.
Część organizacji łączy chmurę z on‑prem przez VPN lub Direct Connect/ExpressRoute. W takim modelu:
- runnery stoją w chmurze,
- systemy testowe częściowo on‑prem, częściowo w chmurze,
- trzeba świadomie projektować routing, by joby nie „woziły” danych tam i z powrotem bez sensu.
Model hybrydowy: nie wszystko musi być self hosted
Bardziej dojrzałe zespoły rzadko idą w „wszystko na self hosted”. Często pipeline dzieli się na:
- joby lekkie (lint, szybkie testy, statyczna analiza) – nadal na SaaS,
- joby ciężkie lub wymagające sieci wewnętrznej – na self hosted.
Przykład:
- PR otwiera pipeline na runnerach SaaS (szybki feedback dla dewelopera),
- merge do głównej gałęzi odpala dodatkowy pipeline na self hosted (pełne testy integracyjne, testy e2e na środowisku zbliżonym do produkcji).
Taki układ zmniejsza:
- koszt self hosted (mniej jobów, krótszy czas działania maszyn),
- zależność od wewnętrznej infrastruktury w codziennej pracy deweloperów,
- ryzyko, że awaria on‑prem zablokuje wszystkie PR‑y.

Główne ryzyka bezpieczeństwa związane z self hosted runnerami
Wykonywanie nieufnego kodu na zaufanej infrastrukturze
Kluczowy problem: runner uruchamia kod, który pochodzi z repozytorium. W publicznych projektach oznacza to wprost: kod dostarczają nieznane osoby. W prywatnych – ryzyko złośliwych commitów nadal istnieje (błędy, konto dewelopera przejęte przez atakującego, biblioteka z backdoorem).
Na self hosted ten kod wykonuje się:
- na serwerach podłączonych do Twojej sieci wewnętrznej,
- często z uprawnieniami wystarczającymi do kontaktu z kluczowymi systemami (bazy, API, kolejki),
- czasem z możliwością odczytu lokalnych plików konfiguracyjnych z tajnymi danymi.
Jeżeli runner zostanie przejęty przez złośliwy pipeline, może posłużyć jako pivot do dalszej eksploracji sieci: skanowanie portów, szukanie słabych usług, wyciąganie danych z systemów testowych.
Reużycie środowiska i wycieki danych między jobami
Przy „długowiecznych” runnerach największym bólem jest brak pełnej izolacji między jobami. Skutki:
- na dysku pozostają fragmenty poprzednich buildów,
- w katalogach roboczych albo cache mogą leżeć klucze, certyfikaty, logi z danymi,
- procesy tła (np. serwery testowe) mogą zacząć „podsłuchiwać” kolejne joby.
Jeśli w tej samej maszynie mieszają się joby z wielu projektów, łatwo o sytuację, w której projekt A czyta dane projektu B. Czasem przypadkiem (błąd konfiguracji ścieżek), czasem wprost z premedytacją.
Uprzywilejowany dostęp do hosta i Docker‑in‑Docker
Wielu adminów uruchamia runnery w trybie:
rootna host OS,- Docker w trybie „privileged” albo z montowaniem socketa
/var/run/docker.sock, - dostęp do całego filesystemu hosta z jobów.
To skraca czas konfiguracji, ale znacząco zwiększa ryzyko:
- złośliwy job może odczytać/zapisać dowolne pliki na hoście,
- osoby z dostępem do pipeline’ów mogą w praktyce eskalować się do administratora hosta,
- w przypadku podatności w Dockerze lub kernelu – ucieczka z kontenera jest znacznie prostsza.
W wielu realnych incydentach vektorem ataku jest właśnie docker.sock lub inne uprzywilejowane API dostępne z poziomu joba.
Nadmierne uprawnienia sieciowe i dostęp do sekretów
Runnery często stoją w tej samej sieci co:
- bazy danych produkcyjne lub staging,
- brokery wiadomości, cache, systemy raportowe,
- wewnętrzne panele admina.
Jeżeli firewall przepuszcza ruch „bo łatwiej tak skonfigurować”, każdy pipeline automatycznie staje się:
- klientem dla tych usług (może pobierać/zmieniać dane),
- potencjalnym wektorem ataku na systemy krytyczne (SQLi, RCE przez testy integracyjne itd.).
Osobny problem to sekrety w zmiennych środowiskowych. Jeżeli:
- sekrety są wstrzykiwane do wszystkich jobów nawet wtedy, gdy nie są potrzebne,
- logi nie są filtrowane (komendy typu
set -xwypisują wszystko na konsolę), - sekrety są współdzielone między projektami „na lenia”,
ryzyko ich wycieku rośnie wykładniczo. W połączeniu z pełnym dostępem sieciowym runner staje się „kluczem do królestwa”.
Kompromitacja tokena runnera i podszywanie się pod agenta
Token, którym runner rejestruje się w platformie CI, jest bardzo wrażliwym elementem:
- pozwala zarejestrować nowego agenta w Twozej infrastrukturze CI,
- w niektórych konfiguracjach daje dostęp do logów i artefaktów historycznych,
- może umożliwić przechwycenie jobów i wykonanie ich na maszynie atakującego.
Scenariusz jest prosty: ktoś przejmuje VM z runnerem (błąd w panelu chmury, brak aktualizacji, słaby SSH), kradnie token z pliku konfiguracyjnego i rejestruje fałszywego runnera. Od tej pory wszystkie joby skierowane do tego poola mogą lądować u atakującego.
Ataki łańcucha dostaw (supply chain) przez CI
Self hosted runner to doskonałe miejsce do:
- wstrzykiwania złośliwych zależności (np. modyfikacja pakietów w lokalnym cache),
- podmiany artefaktów (np. plików .jar, obrazów kontenerów) przed uploadem do registry,
- podpisywania binariów z użyciem przechowywanych kluczy.
Jeżeli w pipeline’ach używany jest np. KMS lub HSM do podpisu artefaktów, kompromitacja runnera może umożliwić podpisanie dowolnego złośliwego kodu jako „oficjalny”. Tego typu incydenty bywają trudne do wykrycia – wszystko pozornie przechodzi standardową ścieżkę release’u.
Projektowanie architektury bezpiecznego self hosted runnera
Rozdzielenie środowisk: produkcja, staging, deweloperskie
Pierwsza zasada: runnery nigdy nie powinny stać w tej samej sieci co produkcja. Nawet jeśli testy „czasem muszą” dotykać produkcji, lepiej zbudować do tego dedykowany, mocno kontrolowany most.
Praktyczny podział:
- pool runnerów dla środowisk deweloperskich i feature branchy – minimalne uprawnienia sieciowe, brak dostępu do wrażliwych danych,
- pool runnerów dla staging/pre‑prod – dostęp tylko do zasobów staging,
- osobny proces deploya do produkcji (najlepiej przez kontrolowany serwis, nie „zwykły job CI”).
Jeśli budżet jest ograniczony, wystarczy na start dwa pule:
- publiczny – bez połączenia z siecią wewnętrzną, obsługuje joby, które nie potrzebują zasobów wewnętrznych,
- wewnętrzny – z minimalnym dostępem do testowej infrastruktury, bez dostępu do produkcji.
Segmentacja sieci i „zero trust light”
Segregacja sieciowa runnerów to prosty sposób na ograniczenie skutków włamania. Dobry cel:
- każdy pool runnerów w osobnej podsieci/VPC,
- listy ACL i security groups pozwalają tylko na:
- połączenia wychodzące do platformy CI (GitHub/GitLab/Azure DevOps),
- połączenia do konkretnych usług testowych (baza, API staging),
- dostęp do storage/registry (artefakty, obrazy).
Jeżeli runner nie musi łączyć się z całym internetem – blokuje się ruch wychodzący poza kilka domen (np. repo zależności, mirror pakietów). Zmniejsza to powierzchnię ataku i utrudnia exfiltrację danych.
Oddzielenie ruchu CI od reszty sieci
Runner sam w sobie rzadko jest celem. Częściej służy jako trampolina do innych systemów. Dlatego opłaca się traktować ruch z runnerów jako osobną kategorię w sieci.
Przy rozsądnym nakładzie pracy można zrobić prosty podział:
- subnet „CI” – tylko runnery i pomocnicze usługi (cache pakietów, mirror repozytoriów),
- subnet „aplikacje testowe” – środowiska test/staging,
- subnet „infrastruktura” – bazy, kolejki, systemy zarządzania.
Między tymi segmentami przechodzą wyłącznie porty i protokoły faktycznie używane w pipeline’ach. Zamiast ogólnego „runner ma dostęp wszędzie”, robi się:
- konkretny port do bazy testowej (np.
5432), - konkretny load balancer dla API staging,
- adresy mirrorów pakietów i rejestru kontenerów.
Dla mniejszych organizacji wystarczy jeden osobny subnet dla runnerów z restrykcyjnymi egress rules i pojedynczym firewallem, który przepuszcza tylko to, co pipeline rzeczywiście wykorzystuje.
Osobne pule runnerów dla różnych typów obciążenia
Kosztowny błąd to wrzucenie wszystkiego do jednego worka: publiczne PR‑y, wewnętrzne buildy, testy produkcyjne. Taniej i bezpieczniej jest wydzielić kilka prostych pul:
- pula „publiczna” – do jobów, gdzie kod przychodzi spoza organizacji (forki, open source),
- pula „zaufana” – tylko dla zamkniętych repo i zespołów wewnętrznych,
- pula „deploy” – niewielka, ściśle kontrolowana, do wykonywania kroków z prawdziwym wpływem na środowisko.
Pierwsza pula powinna być najmocniej ograniczona sieciowo i zminimalizowana pod kątem sekretów. Jeżeli komuś uda się ją przejąć, nie powinien zyskać nic więcej niż tymczasowy dostęp do samej maszyny, która za moment i tak zostanie zniszczona.
Ephemeral vs długowieczne runnery
Z punktu widzenia bezpieczeństwa ideałem są ephemeral runnery:
- maszyna powstaje na czas joba lub niewielkiej paczki jobów,
- po zakończeniu jest niszczona razem z dyskiem,
- każdy pipeline dostaje świeże środowisko.
Taki model świetnie ogranicza:
- wycieki między jobami (brak wspólnego dysku),
- trwałą obecność malware (po jobie VM znika),
- ryzyko, że ktoś będzie się „zagnieżdżał” na runnerze tygodniami.
Problem: koszt i narzut operacyjny. Tworzenie VM na każde uruchomienie może być drogie i wolne, szczególnie w chmurze o wysokich cenach za godzinę i storage.
Rozsądny kompromis na start:
- pule ephemeral tylko dla jobów wysokiego ryzyka (publiczne PR‑y, pipeline’y z dostępem do podpisywania artefaktów),
- pule długowieczne dla wewnętrznych, powtarzalnych jobów, ale z mocnym czyszczeniem po każdym zadaniu.
Jeśli używany jest Kubernetes, tańszą ścieżką są runnery, które tworzą pody per job – cluser K8s jest stały, ale sam kontener z jobem jest krótkotrwały i izolowany.
Runner jako „skrzynka narzędziowa”, nie „klucz do wszystkiego”
Projektując architekturę, łatwo przeciążyć runnery odpowiedzialnością. Zamiast dawać im bezpośredni dostęp do każdego systemu, lepiej wprowadzić proste pośredniki:
- deploy do produkcji przez dedykowany serwis (np. API, które przyjmuje podpisany artefakt i zleca rollout),
- dostęp do baz danych przez bastion / proxy z audytem zapytań,
- interakcje z chmurą przez scoped role (np. dedykowany IAM role per pipeline).
Dzięki temu runner staje się narzędziem do wykonywania komend, a nie superadminem infrastruktury. Nawet przy pełnym przejęciu agenta atakujący napotyka dodatkowe granice.
Monitoring i detekcja nadużyć na runnerach
Bez podstawowego monitoringu trudno wykryć, że runner został użyty nie tak, jak trzeba. Nie trzeba od razu budować SOC – kilka prostych kroków robi sporą różnicę:
- zbieranie logów systemowych i z runtime’u kontenerów do centralnego miejsca,
- proste reguły alertów:
- nietypowy ruch sieciowy (np. skany portów, połączenia do egzotycznych adresów),
- długi czas trwania joba względem mediany,
- nieoczekiwane procesy (np. interaktywna powłoka).
- metryki z hostów: użycie CPU, RAM, IO – skoki w nietypowych godzinach to często „koparka” albo skan.
Przy małym budżecie wystarczy opensource’owy stack (Prometheus + Loki + Grafana, ewentualnie Vector/Fluent Bit) i kilka dashboardów z alertami na e‑mail lub Slacka. Ważne, żeby ktoś faktycznie widział, co robią runnery, a nie tylko czy job „wyszedł na zielono”.
Kontrola dostępu do konfiguracji runnerów
Konfiguracja runnerów (tags, pule, uprawnienia) często jest dostępna z tego samego panelu co reszta CI. Jeśli każdy maintainer projektów może dowolnie przełączać pule lub dodawać tagi, polityki bezpieczeństwa szybko się rozmywają.
Bezpieczniejszy wariant:
- konfiguracja pul runnerów tylko dla wąskiej grupy (DevOps / platform team),
- zespoły mogą korzystać z puli poprzez etykiety, ale nie mogą ich zmieniać,
- istnieją zasady: które repo może używać których pul (np. przez protected runners / allowed projects).
W praktyce oznacza to więcej pracy przy onboardingu nowych projektów, ale zmniejsza ryzyko, że ktoś przypadkiem zacznie budować publiczny fork na runnerze z dostępem do sieci wewnętrznej.
Hardening systemu i środowiska self hosted runnera
Minimalny, aktualny system operacyjny
Dobrym punktem wyjścia jest obraz systemu:
- zredukowany do niezbędnych pakietów (bez GUI, bez zbędnych usług),
- regularnie aktualizowany automatycznie (patch management),
- z prekonfigurowanym logowaniem i agentami monitoringu.
Najprostsze podejście:
- utrzymywać złoty obraz (np. AMI w AWS, template VM w vSphere),
- co miesiąc odświeżać go o patche bezpieczeństwa,
- automatycznie zastępować stare instancje runnerów nowymi z aktualnego obrazu.
Zamiast łatać każdą maszynę ręcznie, taniej jest cyklicznie je wymieniać. Przy okazji zmniejsza się ryzyko „dryfu konfiguracyjnego” między hostami.
Ograniczanie usług systemowych i portów
Na hoście, który ma służyć jako runner, większość domyślnych usług jest zbędna. Przy odrobinie pracy można:
- wyłączyć SSH lub ograniczyć je do dostępu tylko z adresów adminów / bastionu,
- zablokować wszystkie porty przychodzące na firewallu systemowym oprócz absolutnie koniecznych,
- usunąć demony typu drukarki, Avahi, nieużywane serwery HTTP.
Runner w wielu przypadkach wcale nie musi przyjmować połączeń z zewnątrz – wystarczy, że sam łączy się z platformą CI. To mocno ogranicza powierzchnię ataku.
Użytkownicy, sudo i separacja procesów
Dużym źródłem problemów jest uruchamianie wszystkiego jako root. Zwiększa to wygodę, ale też konsekwencje włamania. Bardziej rozsądny układ:
- dedykowany użytkownik systemowy
ci-runnerbez możliwości logowania interaktywnego, - brak pełnego
sudo; jeśli trzeba,sudotylko do kilku konkretnych komend (np. restart lokalnego serwisu), - oddzielenie użytkownika hosta od użytkowników w kontenerach (UID mapowanie, user namespaces).
Jeśli runner korzysta z Dockera, lepiej nie dodawać użytkownika CI do grupy docker na hoście. W praktyce jest to odpowiednik pełnego roota. Lepszym wyborem są narzędzia uruchamiające kontenery przez rootless Docker, Podman lub mechanizmy wbudowane w orchestrator (Kubernetes).
Hardening Dockera i kontenerów
Najczęściej to właśnie warstwa kontenerowa jest najbardziej kuszącym celem. Kilka prostych zasad:
- unikanie trybu
--privileged– zamiast tego, granularne--cap-addtylko tam, gdzie jest to niezbędne, - brak montowania
/var/run/docker.sockdo kontenerów z jobami, - ograniczenia zasobów na kontener (CPU, RAM, I/O) – utrudnia to m.in. kopanie kryptowalut czy DoS hosta.
Dobrym, tanim krokiem jest też stosowanie:
- seccomp/apparmor lub SELinux w trybie enforcing (z predefiniowanymi profilami dla typowych obrazów),
- read‑only filesystem dla kontenerów, które nie muszą nic zapisywać,
- wydzielonych wolumenów tylko dla katalogów, gdzie to konieczne (np. cache pakietów).
Tam, gdzie to możliwe, pipeline’y powinny używać lekkich, dobrze utrzymywanych obrazów bazowych (alpine, distroless, oficjalne obrazy języków), zamiast „złotych obrazów” tworzonych ręcznie przez lata.
Polityki bezpieczeństwa w Kubernetes dla runnerów
Jeśli runnery działają jako kontroler w klastrze K8s i każda job generuje nowy pod, duża część hardeningu może być przeniesiona na poziom klastra:
- Pod Security Standards / Pod Security Admission – zakaz kontenerów uprzywilejowanych, wymóg nie‑roota, ograniczenie dostępu do hostPath,
- NetworkPolicies – pods z jobami CI mogą łączyć się tylko z konkretnymi usługami,
- oddzielne namespaces dla różnych pul runnerów (public, internal, deploy).
Przy małej ekipie nie ma sensu pisać wszystkiego od zera – lepiej przyjąć gotowe zestawy reguł z projektów typu Kyverno, Gatekeeper lub z dokumentacji chmurowej dystrybucji K8s, a potem je dopasowywać do potrzeb.
Bezpieczne przechowywanie i rotacja sekretów
Najbardziej wrażliwe elementy runnera to często nie sam system, tylko sekrety, do których ma dostęp (tokeny, klucze, hasła do rejestru). Kilka praktycznych zasad:
- sekrety nie powinny leżeć na dysku hosta w postaci jawnej (pliki .env, configi w home użytkownika),
- do wstrzykiwania sekretów używać natywnych mechanizmów platformy CI (GitHub Secrets, GitLab CI Variables) lub zewnętrznego sejfu (HashiCorp Vault, AWS Secrets Manager),
- sekrety rozdzielać per projekt, a nie „jeden token dla wszystkich repo”.
Dobrą praktyką jest też:
- rotacja kluczowych sekretów co kilka miesięcy albo przy każdym incydencie bezpieczeństwa,
- użycie krótkotrwałych tokenów (np. OIDC + role w chmurze zamiast statycznych access keys),
- redakcja logów – maskowanie wartości sekretów, blokowanie komend wypisujących całe środowisko.
Czyszczenie środowiska po jobie
Na długowiecznych runnerach często zostają:
- katalogi robocze z buildami,
- cache z dependency z wrażliwymi danymi w konfiguracji,
- logi aplikacji testowych, które zawierają fragmenty ruchu użytkowników.
Zamiast liczyć na ręczną dyscyplinę zespołów, lepiej zautomatyzować sprzątanie:
- skrypty
pre/post-jobusuwające katalog roboczy i tymczasowe pliki, - okresowe czyszczenie cache (np. raz dziennie/tygodniowo) z zachowaniem tylko najnowszych wpisów,
- cykliczny „rebuild” runnerów – po określonej liczbie jobów host jest niszczony i tworzony od nowa z czystego obrazu.
Jeśli w pipeline’ach używane są lokalne bazy testowe czy brokery (np. uruchamiane w kontenerach), ich dane także powinny być kasowane lub inicjalizowane na nowo, zamiast reużywane w nieskończoność.
Walidacja obrazów i narzędzi używanych w pipeline’ach
Najczęściej zadawane pytania (FAQ)
Kiedy naprawdę opłaca się przejść na self hosted runnery?
Self hosted runnery mają sens, gdy masz powtarzalny problem, którego nie da się tanio rozwiązać na runnerach SaaS: bardzo wymagające buildy (GPU, duży RAM), specyficzny system lub biblioteki, konieczność dostępu do zamkniętej sieci wewnętrznej albo rosnący rachunek za minuty, który zaczyna być porównywalny z kosztem własnych maszyn i obsługi.
Jeśli deweloperzy czekają długo w kolejce na współdzielone runnery, a pipeline’y odpalają się dziesiątki razy dziennie, własny pool runnerów potrafi realnie zwiększyć tempo pracy. Gdy jednak minuty u dostawcy są wciąż tanie, a nikt w zespole nie ma czasu ani kompetencji, by utrzymywać infrastrukturę, lepiej skupić się na optymalizacji samych pipeline’ów niż na budowaniu własnych runnerów.
Jak policzyć, czy self hosted runner będzie tańszy niż runner SaaS?
Najprostszy sposób to porównać miesięczny koszt minut CI/CD z przybliżonym kosztem utrzymania własnych maszyn i pracy inżyniera. Z jednej strony masz fakturę od dostawcy (np. GitHub, GitLab), z drugiej: koszt VM-ów/serwerów + szacunkowo kilka–kilkanaście godzin miesięcznie osoby, która zajmie się aktualizacjami, monitoringiem i naprawianiem problemów.
Jeżeli po zsumowaniu tych elementów wychodzi, że własny runner będzie tylko minimalnie tańszy, a środowisko nie ma specjalnych wymagań (GPU, compliance, dostęp do sieci wewnętrznej), migracja zwykle nie ma sensu. Przewaga kosztowa powinna być wyraźna, bo zawsze dochodzi ryzyko incydentów oraz „ukryty koszt” czasu spędzonego na gaszeniu pożarów w CI.
Jakie są największe zagrożenia bezpieczeństwa przy self hosted runnerach?
Runner ma dostęp do kodu, sekretów (tokeny, klucze SSH, hasła), rejestrów Docker i często do środowisk testowych czy produkcyjnych. Przejęcie takiej maszyny daje atakującemu znacznie więcej niż tylko jedno repozytorium – otwiera drogę do infrastruktury, danych i dalszych eskalacji w sieci wewnętrznej.
Typowe ryzyka to m.in. brak aktualizacji systemu na runnerze, zbyt szerokie uprawnienia sieciowe (runner widzi całą sieć firmową), źle zabezpieczone logi z sekretami, a także skompromitowany token rejestrujący runnera w platformie CICD. Ten ostatni pozwala podłączyć „fałszywego” agenta, który zacznie wykonywać joby w imieniu twojej organizacji.
Jak w praktyce zabezpieczyć self hosted runnery w firmowej sieci?
Minimum to traktowanie runnera jak hosta wysokiego ryzyka: regularne aktualizacje systemu, twarde ograniczenie dostępu sieciowego (firewalle, segmentacja VLAN/VPC), brak zbędnych usług na maszynie oraz separacja runnerów używanych do zadań produkcyjnych od tych „developerskich”. Wiele zespołów ustawia osobne runnery do buildów, a inne do deployów z dostępem do wrażliwych środowisk.
Praktycznym i tanim podejściem jest uruchamianie jobów w odizolowanych kontenerach lub krótkotrwałych VM-ach, zamiast wykonywania wszystkiego bezpośrednio na host OS. Ułatwia to szybkie „wyrzucenie” zainfekowanej instancji i odtworzenie jej z czystego obrazu, zamiast długiego i kosztownego dochodzenia, co zostało zmodyfikowane.
Co wybrać: self hosted runner jako serwer, VM czy kontener?
Dla większości zespołów najrozsądniejszy start to maszyna wirtualna (w chmurze lub on-premise). VM łatwo zautomatyzować, klonować i wymieniać na nową, gdy coś pójdzie nie tak. Fizyczny serwer ma sens przy bardzo specyficznych wymaganiach sprzętowych (silne GPU, niestandardowe karty), ale trudniej go elastycznie skalować.
Runner w kontenerze to kolejny krok dla bardziej doświadczonych zespołów – daje dobrą izolację i możliwość uruchamiania jobów w osobnych kontenerach, ale wymaga solidnego ogarnięcia Dockera/Kubernetesa. Warto zaczynać od prostszego modelu (VM) i dopiero po zebraniu doświadczeń wchodzić w bardziej złożoną orkiestrację.
Czy self hosted runner jest konieczny, jeśli potrzebuję dostępu do sieci wewnętrznej (VPN, systemy legacy)?
W większości przypadków tak, bo runnery SaaS nie mają bezpośredniego dostępu do twojej sieci firmowej ani do systemów dostępnych tylko przez VPN lub prywatne połączenia. Próby „przekombinowania” tego przez tunelowanie z chmury często kończą się skomplikowaną, trudną do utrzymania infrastrukturą – droższą niż postawienie kilku własnych runnerów w tej samej sieci, gdzie działają systemy legacy.
Praktyczny wariant „na start” to mały pool runnerów wewnątrz sieci (np. 1–2 VM), które mają ograniczony dostęp tylko do potrzebnych usług: konkretnych baz danych, API, środowisk testowych. Z czasem można to rozbudować, ale lepiej zacząć od wąskiego, dobrze kontrolowanego zakresu niż od razu udostępniać runnerowi całą firmową infrastrukturę.
Czy da się połączyć self hosted runnery z runnerami dostawcy w jednym projekcie?
Tak, sensowne i oszczędne podejście to model hybrydowy. Lekkie zadania (lint, szybkie testy jednostkowe, proste buildy) możesz nadal wykonywać na runnerach SaaS, a tylko ciężkie lub wymagające dostępu do sieci wewnętrznej – na self hosted. W GitHub Actions czy GitLab CI sprowadza się to do odpowiedniego ustawienia runs-on/tagów przy poszczególnych jobach.
Takie rozwiązanie pozwala wyciągnąć maksymalny efekt przy ograniczonym wysiłku: nie utrzymujesz wielkiej floty własnych runnerów, a jednocześnie nie przepalasz minut SaaS na zadania wymagające GPU, dużego RAM czy dostępu do zamkniętych systemów. To często najlepszy kompromis dla zespołów, które dopiero wchodzą w temat własnej infrastruktury CI.
Najważniejsze wnioski
- Self hosted runner ma sens tylko wtedy, gdy rozwiązuje konkretny, powtarzalny problem: wysokie koszty minut SaaS, brak dostępu do sieci wewnętrznej albo wymagające buildy (GPU, dużo RAM, niestandardowe pakiety).
- Przeniesienie runnera „do siebie” oznacza przejęcie pełnej odpowiedzialności za system, aktualizacje, bezpieczeństwo, monitoring i dostęp sieciowy – to nie jest darmowy zamiennik runnerów chmurowych.
- Runner jest elementem krytycznym, bo łączy kod, sekrety i dostęp do środowisk (w tym produkcji); jego przejęcie zwykle daje atakującemu znacznie więcej niż dostęp do pojedynczego repozytorium.
- Token rejestrujący runnera w platformie CICD jest punktem zaufania – jego kompromitacja pozwala podpiąć fałszywego agenta, który będzie wykonywał joby lub modyfikował dane w pipeline’ach.
- Formę runnera (bare-metal, VM, kontener, autoskalująca grupa w chmurze) dobiera się do obciążenia i budżetu; najczęściej wystarcza prosta maszyna wirtualna, którą łatwo zreplikować i wymieniać jak „jednorazówkę”.
- Zanim zespół zainwestuje czas w self hosted, powinien policzyć koszt godzinowy inżyniera vs koszt minut SaaS i sprawdzić, czy naprawdę brakuje mu wydajności lub dostępu do zasobów, a nie po prostu optymalizacji pipeline’ów.
- Jeśli na pytania o realne blokery, kompetencje do utrzymania infrastruktury i jasny powód biznesowy pojawia się „nie” lub „nie wiem”, rozsądniej zostać przy runnerach dostawcy i skupić się na usprawnieniu istniejących procesów.
Bibliografia i źródła
- GitHub Actions: Self-hosted runners. GitHub – Oficjalna dokumentacja self-hosted runnerów GitHub Actions
- GitLab Runner: Installation and autoscaling. GitLab – Dokumentacja GitLab Runner, formy uruchomienia i autoskalowanie
- Azure Pipelines agents: Self-hosted agents. Microsoft – Dokumentacja self-hosted agentów w Azure DevOps Pipelines
- Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley (2010) – Klasyczna książka o praktykach CI/CD i roli pipeline’ów
- The DevOps Handbook: How to Create World-Class Agility, Reliability, and Security in Technology Organizations. IT Revolution Press (2016) – Praktyki DevOps, automatyzacja, bezpieczeństwo i przepływy CI/CD
- NIST SP 800-190: Application Container Security Guide. National Institute of Standards and Technology (2017) – Wytyczne bezpieczeństwa kontenerów wykorzystywanych m.in. jako runnery






