Pierwszy pipeline CI/CD: jak zautomatyzować testy i wdrożenia bez stresu

0
217
2.8/5 - (6 votes)

Nawigacja:

Po co ci CI/CD i kiedy ma to w ogóle sens

CI i CD po ludzku, bez żargonu

CI, czyli ciągła integracja, to prosty nawyk: za każdym razem, gdy wrzucasz kod do repozytorium, automat sprawdza, czy ten kod się buduje i przechodzi podstawowe testy. Nic więcej.

CD, czyli ciągłe dostarczanie lub ciągłe wdrażanie, to kolejny krok: po przejściu testów automat potrafi wypchnąć aplikację na serwer testowy lub produkcyjny, bez klikania i ręcznego kopiowania plików.

W praktyce: CI dba, żeby nie psuć projektu przy każdym commicie, a CD – żeby wdrożenia nie były jednorazową akcją „na spinie”, tylko powtarzalnym procesem.

Jak wygląda życie bez pipeline’u CI/CD

Bez CI/CD wszystko robisz ręcznie. Zmiana w kodzie, szybki „test” na lokalnym środowisku, potem logowanie na serwer, kopiowanie plików przez FTP lub SFTP, nadpisywanie katalogów. Jeśli masz szczęście – działa. Jeśli nie – zaczyna się szukanie, co poszło nie tak.

Typowy scenariusz:

  • zapominasz wrzucić jeden plik lub zmieniasz go nie w tym katalogu,
  • na serwerze jest inna wersja zależności niż u ciebie,
  • w pośpiechu pomijasz uruchomienie testów, „bo przecież zmiana jest mała”.

Przy większej liczbie wdrożeń w tygodniu takie podejście kończy się ciągłym gaszeniem pożarów i strachem przed każdym kolejnym deployem.

Kiedy pipeline ma sens nawet w małym projekcie

Pipeline CI/CD opłaca się szybciej, niż większości osób się wydaje. Wystarczy, że:

  • zmiany wchodzą częściej niż raz na kilka miesięcy,
  • projekt ma więcej niż jedną osobę, która w nim grzebie,
  • masz choćby jeden test, który potrafi złapać głupi błąd przed wdrożeniem.

Nawet przy jednoosobowym, małym projekcie pipeline oszczędza czas. Zamiast odpalać testy „z ręki” (albo o nich zapominać), ustawiasz automat: po każdym pushu, albo chociaż po każdym merge do gałęzi głównej.

Jedyny przypadek, gdy CI/CD można spokojnie odłożyć, to krótki, jednorazowy projekt bez dalszego rozwoju – np. mały skrypt na wewnętrzne potrzeby, który po prostu działa i nie będzie rozwijany.

W jaki sposób CI/CD zmniejsza stres przy wdrożeniach

Najwięcej nerwów przy wdrożeniach wynika z niepewności: czy niczego nie pominąłem, czy środowisko jest takie samo, czy na pewno zrobiłem wszystkie kroki w dobrej kolejności. Pipeline CI/CD zamienia to w przewidywalny rytuał.

Automatyzacja daje kilka bardzo konkretnych efektów:

  • wczesne wychwytywanie błędów – testy odpalają się przy każdym pushu, więc widzisz problemy od razu, a nie dopiero na produkcji;
  • powtarzalność – ta sama sekwencja komend uruchamia się zawsze tak samo, bez zależności od nastroju czy zmęczenia osoby wdrażającej;
  • czytelna historia wdrożeń – w logach pipeline widzisz, co i kiedy weszło, z jakiego commita, kto uruchomił deploy;
  • łatwiejszy rollback – jeśli budujesz wersje jako artefakty lub obrazy, dużo prościej jest cofnąć się do poprzedniej, działającej wersji.

Z czasem pipeline staje się naturalnym filtrem jakości: jeśli coś przechodzi cały zestaw kroków, masz większą pewność, że wdrożenie nie rozleci się w losowym miejscu.

Minimalne podstawy, które trzeba ogarnąć przed pierwszym pipeline

Git i sensowna gałąź główna

Bez Gita nie będzie pipeline’u. Potrzebne jest przynajmniej jedno zdalne repozytorium (na GitHubie, GitLabie lub Bitbuckecie) i jedna gałąź, która będzie traktowana jako główna – najczęściej main lub master.

Kilka prostych zasad startowych:

  • ustal, że tylko kod z gałęzi głównej trafia na produkcję,
  • feature’y rozwijaj na osobnych branchach, scalaj przez pull/merge request,
  • nie commituj bezpośrednio na produkcyjny serwer – wszystko przechodzi przez repozytorium.

Pipeline CI/CD będzie nasłuchiwał zmian właśnie w tym repozytorium i na wybranych gałęziach.

Prosta, przewidywalna struktura projektu

Narzędzie CI/CD musi wiedzieć, gdzie leży kod, testy i pliki konfiguracyjne. Przy prostym projekcie webowym sensowna struktura może wyglądać tak:

  • src/ – kod aplikacji,
  • tests/ – testy jednostkowe / integracyjne,
  • package.json, pyproject.toml, pom.xml itp. – definicja zależności i komend,
  • .github/workflows/ lub .gitlab-ci.yml – konfiguracja pipeline’u.

Im mniej wyjątków i dziwnych ścieżek, tym prościej zdefiniować kroki pipeline’u i tym mniej specjalnych reguł trzeba zapamiętywać.

Chociaż jeden sensowny test automatyczny

Na początku nie jest potrzebne pełne pokrycie testami. Wystarczy jeden test, który naprawdę coś sprawdza. Ważne, żeby:

  • dało się go uruchomić jedną komendą (np. npm test, pytest, mvn test),
  • kończył się kodem wyjścia 0 przy sukcesie, różnym od 0 przy błędzie (to standard dla narzędzi testowych),
  • łapał najbardziej bolesny błąd z twojej perspektywy: brak odpowiedzi API, błędny status HTTP, zły wynik prostej funkcji biznesowej.

Pipeline będzie po prostu odpalał tę komendę na każdym pushu. Z czasem zestaw testów można rozbudowywać, ale nie ma sensu czekać z CI/CD, aż projekt będzie w pełni pokryty testami.

Konto na platformie z CI/CD

Dla prostego startu wystarczy konto na jednym z trzech najpopularniejszych serwisów:

  • GitHub – wbudowane GitHub Actions, dużo gotowych workflow, ogromna społeczność,
  • GitLab – GitLab CI, sensowny wybór dla firm, które hostują własny GitLab,
  • Bitbucket – Bitbucket Pipelines, proste, zgrabne, jeśli kod już leży w Bitbuckecie.

Na początku nie ma potrzeby instalowania własnych serwerów CI. Wystarczy to, co daje dostawca hostujący kod – szczególnie przy małym projekcie lub jednoosobowym zespole.

Prosta mini-aplikacja jako przykład

Dla konkretów przyjmijmy przykładowy projekt:

  • Node.js + Express – małe API zwracające status aplikacji,
  • komenda testowa: npm test,
  • repozytorium na GitHubie, gałąź główna main,
  • hosting na testowym serwerze VPS z dostępem SSH.

Wszystkie przykłady konfiguracji pipeline’u można łatwo przełożyć na inne technologie, podmieniając tylko komendy build/test/deploy.

Osoba pracuje na laptopie przy biurku z podpisanymi płytami CD
Źródło: Pexels | Autor: cottonbro studio

Wybór narzędzia: GitHub Actions, GitLab CI czy coś innego

Proste kryteria wyboru dla początkujących

Na start nie trzeba szczegółowego porównania wszystkich funkcji. Wystarczy kilka prostych pytań:

  • Gdzie leży repozytorium? Jeśli na GitHubie – GitHub Actions. Jeśli na GitLabie – GitLab CI. Jeśli na Bitbuckecie – Bitbucket Pipelines.
  • Czy w projekcie jest własny serwer GitLab w firmie? Jeśli tak – najwygodniej zostać przy GitLab CI.
  • Ile osób pracuje nad projektem? Przy jednoosobowym projekcie wygoda i prostota są ważniejsze niż „zaawansowane funkcje”.

Najczęściej najlepszą decyzją jest: użyć narzędzia CI/CD wbudowanego w platformę, na której jest kod. Unika się w ten sposób dodatkowej integracji i osobnego logowania.

Porównanie GitHub Actions, GitLab CI i innych

Dla prostego pipeline’u różnice sprowadzają się głównie do składni plików konfiguracyjnych i nazewnictwa. Z perspektywy początkującego istotne elementy zestawia poniższa tabela.

PlatformaPlik konfiguracyjnyNazwa jednostkiPlusy na start
GitHub Actions.github/workflows/*.ymlworkflow / jobogromna liczba gotowych akcji, łatwa integracja z publicznymi repozytoriami
GitLab CI.gitlab-ci.ymlpipeline / job / stageczytelna koncepcja stage’ów, dobra integracja z self-hosted GitLabem
Bitbucket Pipelinesbitbucket-pipelines.ymlpipeline / stepprosty model konfiguracji, wygodne dla małych zespołów na Bitbuckecie

Do tego dochodzą inne narzędzia (CircleCI, Jenkins, Drone, Travis), ale przy pierwszym pipeline nie ma sensu dokładać sobie dodatkowej warstwy. Im mniej elementów, tym łatwiej opanować podstawy.

Koszty, limity i prywatne repozytoria

Każda z popularnych platform ma darmowy pakiet z limitem minut na pipeline’y i możliwością pracy na prywatnych repozytoriach. Dla jednoosobowego projektu lub małego zespołu te limity zwykle wystarczają na długo.

Kilka praktycznych wskazówek:

  • pilnuj, żeby pipeline nie odpalał się bez potrzeby (np. na każdej gałęzi, w tym na eksperymentalnych),
  • cache’uj zależności (np. katalog node_modules lub .m2), żeby skrócić czas wykonania,
  • nie buduj całej aplikacji przy każdym małym commicie, jeśli nie ma takiej potrzeby.

Przy pierwszym pipeline zużycie minut jest zwykle śmiesznie małe. Większe znaczenie ma to, jak szybko pipeline kończy się zielono lub czerwono i czy komunikaty są czytelne.

Dobry domyślny wybór

Jeśli kod jest na GitHubie, a w zespole nie ma nikogo, kto ma mocne preferencje inne niż „niech po prostu działa”, najlepiej od razu postawić na GitHub Actions. Dla GitLaba analogicznym wyborem będzie GitLab CI.

Późniejsza migracja między narzędziami jest możliwa, ale na start lepiej skupić się na zrozumieniu logiki pipeline’u niż na dyskusji, która platforma jest „obiektywnie lepsza”.

Anatomia prostego pipeline’u CI/CD – z czego to się składa

Podstawowe pojęcia: job, stage, pipeline, artefakt, runner

Kilka terminów przewija się w każdej platformie, nawet jeśli nazwy minimalnie się różnią.

  • job – pojedyncze zadanie, np. „uruchom testy” albo „zbuduj frontend”; składa się z kilku komend wykonywanych w danym środowisku,
  • stage – etap, w którym może być kilka jobów (np. build, test, deploy),
  • pipeline (workflow) – cała sekwencja etapów i jobów uruchamiana jednym wyzwalaczem (np. push na main),
  • artefakt – wynik joba, który można przekazać dalej, np. zbudowana paczka aplikacji lub pliki statyczne,
  • runner – maszyna (wirtualna lub fizyczna), na której faktycznie odpalane są komendy z pipeline’u.

Na początku wystarczy ogarnąć: pipeline składa się z kilku jobów ułożonych w określonej kolejności, a runner to „komputer w chmurze”, który te joby wykonuje.

Typowy przebieg pipeline’u: trigger → build → test → deploy

Najprostszy, sensowny przepływ wygląda tak:

  1. Trigger – coś wyzwala pipeline, np. push na gałąź główną lub stworzenie tagu z wersją.
  2. Build – jeśli aplikacja wymaga budowania (kompilacja, bundlowanie frontendu, budowa obrazu Docker), powstaje zbudowana wersja.
  3. Test – automat uruchamia zestaw testów, korzystając z wyniku builda.
  4. Deploy – po przejściu testów pipeline może wypchnąć nową wersję na środowisko testowe lub produkcyjne.

Nie każdy projekt wymaga czterech kroków. Dla prostego API w dynamicznym języku często wystarczą: testy → deploy, bez jawnego etapu build.

Różnica między CI a CD na przykładzie

Dobrze jest nie mieszać w głowie tych dwóch obszarów.

CI w praktyce, CD w praktyce

Najprostsze rozróżnienie:

  • CI – wszystko, co dzieje się przed decyzją „to może iść na środowisko”,
  • CD – wszystko, co dzieje się od momentu „chcemy to wdrożyć” aż po działającą wersję.

CI kończy się wtedy, gdy masz zielone testy i gotowy artefakt. CD zaczyna się, gdy ten artefakt ląduje na serwerze, w klastrze Kubernetes albo w usłudze typu Heroku.

Przy pierwszym pipeline wygodnie jest oddzielić te dwa światy:

  • jeden workflow/pipeline, który robi build + testy,
  • drugi, który robi samo wdrożenie, wywoływany tylko w określonych sytuacjach (np. push do main albo nowy tag).

Co naprawdę musi się znaleźć w pierwszym pipeline

Na początek wystarczą trzy elementy:

  1. pobranie kodu z repozytorium,
  2. instalacja zależności,
  3. uruchomienie komendy testowej.

Bez skomplikowanych warunków, matryc systemów operacyjnych, osobnych jobów na różne wersje Node’a. Celem jest informacja: „ten commit przechodzi podstawowe testy”.

Pudełko z uporządkowanymi płytami CD z widocznymi tytułami albumów
Źródło: Pexels | Autor: Brett Jordan

Pierwszy pipeline CI: uruchamianie testów po każdym commicie

GitHub Actions – minimalny workflow z testami

Dla przykładowej aplikacji Node.js na GitHubie wystarczy plik .github/workflows/ci.yml z taką zawartością:

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

Ten plik robi jedną rzecz: przy każdym pushu lub pull requeście na main odpala testy na Ubuntu z wybraną wersją Node.js.

Kilka drobnych decyzji, które ułatwiają życie

Proste zmiany robią różnicę:

  • trigger na pull_request – błąd wychwycisz, zanim merge trafi do main,
  • npm ci zamiast npm install – szybsze i powtarzalne instalacje zależności,
  • konkretna wersja Node.js – unikniesz niespodzianek typu „lokalnie mam inną wersję runtime’u”.

Najczęstsze problemy przy pierwszym odpaleniu

Typowe sytuacje przy pierwszym pipeline:

  • brak pliku package-lock.jsonnpm ci się wykrzaczy,
  • testy wymagają bazy danych, a workflow jej nie ma,
  • komenda npm test w package.json jest nieustawiona lub wychodzi zawsze kodem 0.

Na początek opłaca się uprościć testy tak, aby pipeline nie potrzebował zewnętrznych usług. Wystarczy test czysto jednostkowy lub prosty test HTTP do lokalnie odpalonego serwera w tym samym jobie.

Jak traktować czerwony pipeline

Dobrą praktyką jest zasada: czerwony pipeline blokuje merge. Nawet w małym projekcie.

Jeżeli testy zaczynają „czasem przechodzić, czasem nie”, trzeba to naprawić od razu: wyłączyć niestabilny test, naprawić dane wejściowe, dopisać setup bazy. Inaczej szybko wylądujesz w sytuacji, w której nikt nie patrzy na wynik CI.

Rozszerzenie o build: tworzenie artefaktów i przygotowanie do wdrożeń

Kiedy build ma sens

Build ma sens wtedy, gdy rezultat kompilacji/bundlowania:

  • jest ciężki lub czasochłonny do zbudowania,
  • ma iść dokładnie w tej formie na serwer (frontend, binarka, obraz Docker),
  • potrzebujesz spójności: to, co przetestowałeś, ma być tym, co wdrażasz.

Przy prostym Node API bez transpilacji można przez jakiś czas pomijać etap build. Jeśli jednak używasz TypeScriptu albo budujesz frontend, build jako osobny krok szybko staje się sensowny.

Dodanie kroku build w GitHub Actions

Do istniejącego workflow można dołożyć osobny job build, który tworzy artefakt, a testy korzystają z jego wyniku.

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Build app
        run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-build
          path: dist/

  test:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: app-build
          path: dist/

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

W tym wariancie:

  • job build tworzy katalog dist/ i wysyła go jako artefakt,
  • job test czeka na build (needs: build) i pobiera gotowy build.

Artefakty, które przydadzą się przy CD

Artefaktem nie musi być tylko katalog z plikami frontendu. Przy prostym Node API może to być np. spakowana aplikacja:

tar -czf app.tar.gz package.json package-lock.json dist/

Takie archiwum jest łatwe do przesłania na serwer i rozpakowania w katalogu docelowym. Ten sam plik można też podłączyć do kolejnych etapów pipeline’u (np. wdrożenia na staging).

Cache zależności dla szybszego pipeline’u

Przy każdym buildzie instalowanie wszystkich paczek od zera spowalnia feedback. Prosty cache dla Node’a w GitHub Actions:

      - name: Cache node modules
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('package-lock.json') }}
          restore-keys: |
            npm-

Taki cache można wpiąć przed krokiem npm ci. Gdy package-lock.json się nie zmienia, instalacja jest dużo szybsza.

Dłonie przy biurku z płytami CD w technicznym otoczeniu
Źródło: Pexels | Autor: cottonbro studio

Pierwsze proste CD: automatyczne wdrożenie na środowisko testowe / staging

Co oznacza „proste” wdrożenie

Przy serwerze VPS z SSH „proste CD” to najczęściej:

  1. zalogowanie się na serwer,
  2. pobranie artefaktu (albo aktualnego kodu z main),
  3. instalacja zależności na serwerze,
  4. restart procesu aplikacji (np. przez PM2 lub systemd).

W pipeline sprowadza się to do jednego joba: deploy-staging, który wykona sekwencję komend przez SSH.

Bezpieczne trzymanie danych dostępowych

Hasła i klucze prywatne trafiają do sekretów platformy CI, nigdy do repozytorium.

  • w GitHubie: Settings → Secrets and variables → Actions → New repository secret,
  • tworzysz np. STAGING_HOST, STAGING_USER, STAGING_SSH_KEY.

W workflow odwołujesz się do nich przez ${{ secrets.NAZWA_SEKRETU }}.

Przykładowy job deploy na serwer VPS (GitHub Actions)

Do istniejącego pliku workflow można dodać job wdrożeniowy, który odpala się tylko przy pushu do main (bez pull requestów):

jobs:
  # ... build, test jak wcześniej

  deploy-staging:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: Set up SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.STAGING_SSH_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan -H ${{ secrets.STAGING_HOST }} >> ~/.ssh/known_hosts

      - name: Build artifact
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run build
      - run: tar -czf app.tar.gz package.json package-lock.json dist/

      - name: Copy artifact to server
        run: scp app.tar.gz ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }}:/var/www/app/app.tar.gz

      - name: Deploy on server
        run: |
          ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} 
            'cd /var/www/app && 
             tar -xzf app.tar.gz && 
             npm ci --only=production && 
             pm2 reload app || pm2 start dist/server.js --name app'

To jest wersja minimalna. W jednym jobie:

  • konfigurujesz klucz SSH,
  • budujesz aplikację i pakujesz ją,
  • kopiujesz paczkę na serwer,
  • aktualizujesz kod i restartujesz proces.

Jak nie zniszczyć środowiska testowego

Kilka drobiazgów zmniejsza ryzyko:

  • osobny użytkownik na serwerze tylko do aplikacji (bez sudo),
  • osobny katalog, np. /var/www/app-staging,
  • kopie zapasowe pliku konfiguracyjnego lub environmentu (np. .env.staging) przed restartem.

Przy problemach można szybko wrócić do poprzedniej paczki lub przełączyć się na stary katalog aplikacji.

Warunek uruchamiania CD

Nie ma sensu wdrażać na staging każdej gałęzi. Najczęściej wystarcza:

  • deploy przy pushu do main,
  • ewentualnie deploy przy tworzeniu taga staging-vX.Y, jeśli chcesz większej kontroli.

Warunki można ustawiać w sekcji on: albo przy jobie przez if:. Kluczowe jest, aby deploy nie odpalał się „przez przypadek” z feature brancha.

Podejście do produkcji: jak wdrażać, żeby nie bać się commita

Oddzielenie pipeline’u produkcyjnego od stagingowego

Bezpieczne podejście to dwa różne mechanizmy:

  • staging – automatycznie z main,
  • produkcja – wyłącznie po świadomym ruchu: tagu, ręcznym odpaleniu joba, merge’u do specjalnej gałęzi.

W ten sposób każda zmiana ląduje na stagingu, ale nie każda trafia na produkcję.

Trigger produkcji przez tag wersji

Niezawodny wzorzec: nowa wersja produkcyjna = nowy tag. W GitHub Actions można to zdefiniować tak:

on:
  push:
    tags:
      - 'v*.*.*'   # np. v1.2.3

Wtedy workflow odpala się tylko, gdy wypchniesz tag w formacie semver. Dzięki temu:

  • masz jasny ślad, która wersja trafiła na produkcję,
  • łatwo wrócić do poprzedniego taga, jeśli trzeba zrobić rollback.

Manualna akceptacja przed deployem

Przy małym zespole czasem wystarczy prosty „przycisk” w pipeline. Np. w GitLab CI można użyć joba z when: manual, a w GitHub Actions – environments z wymaganym approvalem.

Przykład środowiska produkcyjnego w GitHubie:

  • tworzysz environment production,
  • ustawiasz listę osób, które mogą zatwierdzić deploy,
  • w jobie deploy dodajesz environment: production.

Pipeline dojdzie do kroku deploy, po czym zatrzyma się i poczeka na ręczną zgodę.

Minimalny bezpieczny proces na VPS

Bez skomplikowanej infrastruktury można mieć prosty, ale sensowny model:

  1. commit → pipeline CI (testy, build),
  2. merge do main → automatyczne wdrożenie na staging,
  3. tag vX.Y.Z → pipeline produkcyjny,
  4. krótka weryfikacja po wdrożeniu (ręcznie lub prostym health checkiem).

Nawet jedna prosta komenda typu curl https://twoja-domena.pl/health odpalana jako krok po deployu potrafi szybko wychwycić oczywiste problemy.

Podstawy rollbacku bez skomplikowanych narzędzi

Prosty mechanizm powrotu do poprzedniej wersji

Najmniej bolesny rollback na pojedynczym serwerze to trzymanie kilku ostatnich wersji aplikacji obok siebie. Przykładowa struktura:

/var/www/app-releases/
  2024-01-01_12-00-01/
  2024-01-02_09-15-10/
  current -> 2024-01-02_09-15-10/

Każde wdrożenie tworzy nowy katalog z timestampem i podmienia symlink current. Serwis (np. Nginx, systemd) wskazuje zawsze na current.

Rollback to samo przestawienie symlinka i restart procesu. Przykład komend na serwerze:

cd /var/www/app-releases
ln -sfn 2024-01-01_12-00-01 current
pm2 reload app

Modyfikacja joba deploy pod wiele wersji

Job deployujący może automatyzować tworzenie katalogu wersji. W wariancie stagingowym:

      - name: Copy and extract artifact to releases dir
        run: |
          ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} '
            set -e
            RELEASE_DIR=/var/www/app-releases/$(date +%Y-%m-%d_%H-%M-%S)
            mkdir -p "$RELEASE_DIR"
          '
          scp app.tar.gz ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }}:/var/www/app-releases/app.tar.gz
          ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} '
            set -e
            cd /var/www/app-releases
            RELEASE_DIR=$(ls -td 20* | head -n1)
            mv app.tar.gz "$RELEASE_DIR"/
            cd "$RELEASE_DIR"
            tar -xzf app.tar.gz
            ln -sfn "$RELEASE_DIR" /var/www/app-current
            cd /var/www/app-current
            npm ci --only=production
            pm2 reload app || pm2 start dist/server.js --name app
          '

Serwer www wskazuje wtedy na /var/www/app-current. Zmiana wersji to tylko korekta symlinka.

Ograniczanie liczby starych wydań

Na małym VPS dysk szybko się zapełnia. Do joba deploy można dorzucić prostą rotację:

cd /var/www/app-releases
ls -td 20* | tail -n +6 | xargs -r rm -rf

Pozostaje np. 5 ostatnich wersji. Wystarczająco na rollback, bez śmieci sprzed roku.

Health check po deployu i automatyczny rollback

Jeśli endpoint health działa przewidywalnie, można wymusić jego przejście tuż po wdrożeniu:

      - name: Run health check
        run: |
          for i in {1..10}; do
            STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.twoja-domena.pl/health || echo 000)
            if [ "$STATUS" = "200" ]; then
              echo "Health check OK"
              exit 0
            fi
            echo "Health check failed with $STATUS, retrying..."
            sleep 5
          done
          echo "Health check failed after retries"
          exit 1

Gdy ten krok padnie, job oznacza się jako nieudany. Przy prostym środowisku rollback trzeba zrobić ręcznie, ale jest jasny sygnał, że nowa wersja nie przeszła.

Środowisko produkcyjne jako osobny environment

GitHub Actions pozwala przypiąć deploy do environmentu, który ma własne sekrety i reguły dostępu:

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    needs: test
    if: startsWith(github.ref, 'refs/tags/v')
    environment:
      name: production
      url: https://twoja-domena.pl

Do environmentu production można w panelu przypisać wymaganą akceptację. Pipeline zatrzyma się, dopóki ktoś uprawniony nie kliknie „Approve and deploy”.

Rozdzielenie sekretów staging/production

Dane dostępu produkcji nie powinny przeciekać do jobów stagingowych. Najprostszy podział:

  • sekrety STAGING_* na poziomie repozytorium,
  • sekrety PROD_* w environmentcie production.

W jobie produkcyjnym klucze pobierasz z secrets środowiska, np. ${{ secrets.PROD_SSH_KEY }}. Stagingowy job nie ma do nich dostępu.

Oddzielne workflow dla stagingu i produkcji

Z czasem wygodniej rozdzielić pliki workflow:

  • .github/workflows/staging.yml – testy + deploy na staging z main,
  • .github/workflows/production.yml – testy (ewentualnie skrócone) + deploy z tagów.

Konfiguracja jest wtedy czytelniejsza. Łatwiej też wymusić inny zestaw kroków na produkcji (np. dodatkowy backup bazy).

Minimalny backup przed produkcją

Przed podmianą aplikacji często wystarczy prosty snapshot bazy i konfiguracji. W jobie deploy można to umieścić tuż przed wywołaniem restartu:

ssh $PROD_USER@$PROD_HOST '
  set -e
  BACKUP_DIR=/var/backups/app/$(date +%Y-%m-%d_%H-%M-%S)
  mkdir -p "$BACKUP_DIR"
  cp /etc/systemd/system/app.service "$BACKUP_DIR"/
  pg_dump "$DB_URL" > "$BACKUP_DIR"/db.sql
'

Przy awarii można szybko odtworzyć konfigurację i dane z ostatniego snapshotu, bez szukania w starych notatkach.

Jak nie przeciążyć małego zespołu procesem

Na starcie wystarczy kilka prostych reguł:

  • każdy merge do main musi przejść testy,
  • na staging zawsze idzie aktualny main,
  • produkcję uruchamia się tylko tagiem + manualnym zatwierdzeniem,
  • po deployu ktoś realnie wchodzi na aplikację i wykonuje krótką checklistę (logowanie, podstawowe akcje).

To wciąż mało biurokracji, a ryzyko zaskoczeń w poniedziałek rano spada wyraźnie.

Małe usprawnienia, które szybko zwracają się w CI/CD

Gdy podstawowy pipeline działa stabilnie, sens mają drobne dodatki:

  • równoległe joby testów (np. unit + e2e w osobnych jobach),
  • oddzielne workflow dla lintowania, odpalane na każde PR,
  • generowanie i podpinanie raportów z testów (np. JUnit) do interfejsu CI,
  • automatyczne oznaczanie commita wersją (np. dopisywanie vX.Y.Z do changeloga).

Każdy z tych elementów można dołożyć osobno, bez rozwalania już działającego ciągu: commit → testy → staging → produkcja.

Najczęściej zadawane pytania (FAQ)

Co to jest pipeline CI/CD w prostych słowach?

Pipeline CI/CD to zestaw kroków, który uruchamia się automatycznie po zmianie kodu w repozytorium. Najczęściej buduje aplikację, odpala testy i – jeśli wszystko przejdzie – wdraża nową wersję na serwer.

Dzięki temu nie musisz ręcznie logować się na serwer, kopiować plików i pamiętać o każdej komendzie. Robisz push do Gita, a resztą zajmuje się automat.

Kiedy opłaca się wdrożyć CI/CD w małym projekcie?

CI/CD ma sens, gdy zmiany w projekcie pojawiają się częściej niż raz na kilka miesięcy albo gdy nad kodem pracuje więcej niż jedna osoba. Wtedy automatyczne testy i wdrożenia szybko oszczędzają czas i nerwy.

Nawet w jednoosobowym projekcie pipeline jest przydatny, jeśli choć od czasu do czasu coś poprawiasz lub rozwijasz. Wyjątek to mały, jednorazowy skrypt, który po prostu ma zadziałać i pójść w zapomnienie.

Czy potrzebuję dużo testów, żeby zacząć z CI/CD?

Nie. Na start wystarczy choć jeden sensowny test, który da się uruchomić jedną komendą (np. npm test, pytest) i który faktycznie potrafi złapać realny błąd. Resztę możesz dopisać później.

Ważne, żeby test zwracał poprawny kod wyjścia procesu: 0 przy sukcesie i różny od 0 przy błędzie. Dzięki temu narzędzie CI/CD wie, czy zatrzymać pipeline, czy iść dalej.

GitHub Actions czy GitLab CI – co wybrać na początek?

Najprostsza zasada: użyj tego, co jest wbudowane w platformę, na której trzymasz kod. GitHub → GitHub Actions, GitLab → GitLab CI, Bitbucket → Bitbucket Pipelines.

Dla początkującego różnice sprowadzają się głównie do składni pliku YAML i nazewnictwa (workflow, job, stage). Na poziomie pierwszego pipeline’u możliwości są bardzo podobne.

Jakie są minimalne wymagania, żeby uruchomić pierwszy pipeline CI/CD?

Potrzebujesz kilku podstaw:

  • repozytorium Git na GitHubie, GitLabie lub Bitbuckecie,
  • ustaloną główną gałąź (np. main), z której idzie produkcja,
  • prostą strukturę projektu z wyraźnie wydzielonym kodem i testami,
  • jedną komendę do uruchamiania testów,
  • konto na platformie z CI/CD (zwykle to samo, gdzie leży kod).

Na początek nie jest potrzebny własny serwer CI ani rozbudowana infrastruktura. Wystarczy to, co daje dostawca hostujący repozytorium.

Jak CI/CD konkretnie zmniejsza stres przy wdrożeniach?

Pipeline usuwa ręczne, podatne na pomyłki kroki. Te same komendy budujące, testujące i wdrażające uruchamiają się zawsze tak samo, niezależnie od tego, kto robi deploy i o której godzinie.

Dostajesz też wczesną informację o błędach (testy przy każdym pushu), czytelną historię wdrożeń w logach oraz prostszy rollback, jeśli budujesz wersje jako artefakty lub obrazy. Z czasem wdrożenia stają się rutyną, a nie „akcją specjalną”.

Czy CI/CD ma sens, jeśli wdrażam ręcznie przez FTP i „działa od lat”?

Przy rzadkich zmianach może to jakoś funkcjonować, ale rośnie ryzyko: łatwo zapomnieć o jednym pliku, pomylić katalog albo pominąć testy „bo zmiana jest mała”. Im częściej wdrażasz, tym częściej takie potknięcia będą się mściły.

Prosty pipeline (build + test + wdrożenie na serwer testowy lub produkcyjny) usuwa te powtarzalne błędy. Zamiast liczyć na pamięć i szczęście, opierasz się na sprawdzalnym, powtarzalnym procesie.