Wdrożenie ręczne wygląda zawsze tak samo: ktoś loguje się na serwer, pobiera kod, uruchamia budowanie, restartuje aplikację. Działa - dopóki ten ktoś pamięta wszystkie kroki, robi je w tej samej kolejności i nie wdraża w piątek o siedemnastej.
Kuźnia legionowa nie polegała na pamięci kowala. Miała ustalony porządek: ta sama stal, ta sama temperatura, ten sam hartunek, ten sam znak na każdym ostrzu. Dzięki temu miecz wykuty w Galii pasował do pochwy zrobionej w Rzymie. CI/CD to ten porządek zapisany w pliku.
CI/CD oznacza Continuous Integration / Continuous Deployment - ciągłą integrację i ciągłe wdrażanie. Nie „Container", nie „Component", nie „Code": obie litery odnoszą się do procesu, nie do tego, co się przetwarza.
Każda połowa rozwiązuje inny problem. Continuous Integration to automatyczne budowanie i testowanie kodu po każdej zmianie - żeby błąd wyszedł na jaw w ciągu minut, a nie po tygodniu, gdy nikt już nie pamięta, czyja zmiana go wprowadziła. Continuous Deployment to automatyczne wdrożenie zmiany, która przeszła weryfikację - żeby droga od gotowego kodu do produkcji nie zależała od tego, czy ktoś ma czas.
Narzędzi jest wiele; zostaniemy przy jednym. GitHub Actions automatyzują build, test i deployment po każdym push i pull requeście (PR) - to cała ich rola.
Trzy nieporozumienia warto odsunąć od razu. Nie służą wyłącznie do zarządzania zgłoszeniami (issues) - to osobna funkcja GitHuba. Nie są dostępne tylko dla repozytoriów prywatnych; publiczne korzystają z nich za darmo i bez limitu minut. I nie zastępują Dockera - przeciwnie, to właśnie w kroku workflow wywołujesz
docker build i docker push, więc jedno korzysta z drugiego.Konfiguracja to plik YAML w katalogu
.github/workflows/:1# .github/workflows/deploy.yml
2name: Deploy
3
4on:
5 push:
6 branches:
7 - mainPowyższy fragment to trigger - wskazanie zdarzenia uruchamiającego workflow. Zapisuje się go w czterech zagnieżdżeniach, zawsze w tej kolejności:
otwiera sekcję zdarzeń, on:
wskazuje konkretne zdarzenie, push:
zawęża je do wybranych gałęzi, a branches:
jest pozycją listy tych gałęzi.- main
W YAML-u znaczenie ma wcięcie - każdy kolejny poziom przesuwa się o dwie spacje w prawo i to ono buduje strukturę. Myślnik przed
main oznacza element listy, więc kolejne gałęzie dopisujesz w następnych liniach.Zdarzeń jest więcej niż jedno.
pull_request: uruchomi sprawdzenie przy każdym PR - i to jest zwykle ważniejsze niż push, bo pozwala wychwycić błąd zanim zmiana trafi do głównej gałęzi.Właściwa praca dzieje się w zadaniu (
job), a jego kroki mają ustaloną kolejność:1jobs:
2 build:
3 runs-on: ubuntu-latest
4 steps:
5 - uses: actions/checkout@v4
6
7 - uses: actions/setup-node@v4
8 with:
9 node-version: 20
10
11 - run: npm ci
12 - run: npm test
13 - run: npm run build
14 - run: docker push registry.imperium.rome/legion-api:latestKolejność etapów nie jest kwestią gustu: instalacja zależności (
) → linter i testy (npm ci
) → budowanie aplikacji (npm test
) → wdrożenie na produkcję (npm run build
).docker push
Rządzi nią jedna zasada: im tańszy etap, tym wcześniej. Linter kończy się w kilka sekund, testy w kilka minut, budowanie obrazu w kilkanaście. Gdyby budowanie szło przed testami, każdy literówkowy błąd kosztowałby pełny czas budowania - a i tak zakończyłby się porażką.
Dwa kroki na początku są przygotowaniem, nie etapem pipeline'u.
actions/checkout pobiera kod repozytorium do maszyny, na której workflow działa - bez tego katalog jest pusty. actions/setup-node instaluje wskazaną wersję Node.js i tym samym rozwiązuje spór „u mnie działa": wersja jest zapisana w pliku, więc każdy przebieg dostaje tę samą.Zwróć uwagę na
npm ci zamiast npm install. Pierwsze instaluje dokładnie to, co zapisano w package-lock.json, i przerywa pracę przy rozjeździe z package.json. Drugie może po cichu podnieść wersję zależności - a wtedy CI testuje coś innego niż to, co masz u siebie.Kroki wykonują się po kolei i pierwszy niepowodzenie zatrzymuje resztę. To zachowanie domyślne i pożądane: skoro testy nie przechodzą, budowanie obrazu i wysyłanie go do rejestru byłoby tylko marnowaniem czasu, a w najgorszym razie wdrożeniem zepsutej wersji.
Stąd praktyczny podział. Sprawdzenia - instalacja, linter, testy - uruchamiaj przy każdym push i każdym PR, na każdej gałęzi. Wdrożenie ogranicz do gałęzi głównej, przez
branches: - main z poprzedniej sekcji. Dzięki temu praca nad funkcją jest sprawdzana od pierwszego commita, ale nic nie trafia na produkcję, dopóki zmiana nie zostanie scalona.Kuźnia hartuje każde ostrze tak samo, @name:
.github/workflows/ i jest plikiem YAML, w którym strukturę buduje wcięcie,on: → push: → branches: → - main; pull_request: sprawdza zmianę przed scaleniem,npm ci → npm test → npm run build → docker push, od najtańszego do najdroższego,actions/checkout pobiera kod, actions/setup-node ustala wersję Node.js jednakową dla każdego przebiegu,npm ci instaluje dokładnie to, co w package-lock.json; npm install może po cichu zmienić wersje,Do CI/CD wrócimy w module o infrastrukturze, przy budowaniu na wielu wersjach naraz, artefaktach i cache'owaniu zależności. A na razie zapamiętaj: pipeline nie jest po to, żeby wdrażać szybciej. Jest po to, żeby wdrażać tak samo - a szybkość wychodzi przy okazji.