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.xmlitp. – 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.

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.
| Platforma | Plik konfiguracyjny | Nazwa jednostki | Plusy na start |
|---|---|---|---|
| GitHub Actions | .github/workflows/*.yml | workflow / job | ogromna liczba gotowych akcji, łatwa integracja z publicznymi repozytoriami |
| GitLab CI | .gitlab-ci.yml | pipeline / job / stage | czytelna koncepcja stage’ów, dobra integracja z self-hosted GitLabem |
| Bitbucket Pipelines | bitbucket-pipelines.yml | pipeline / step | prosty 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_moduleslub.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:
- Trigger – coś wyzwala pipeline, np. push na gałąź główną lub stworzenie tagu z wersją.
- Build – jeśli aplikacja wymaga budowania (kompilacja, bundlowanie frontendu, budowa obrazu Docker), powstaje zbudowana wersja.
- Test – automat uruchamia zestaw testów, korzystając z wyniku builda.
- 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
mainalbo nowy tag).
Co naprawdę musi się znaleźć w pierwszym pipeline
Na początek wystarczą trzy elementy:
- pobranie kodu z repozytorium,
- instalacja zależności,
- 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”.

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 domain, npm cizamiastnpm 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.json–npm cisię wykrzaczy, - testy wymagają bazy danych, a workflow jej nie ma,
- komenda
npm testwpackage.jsonjest 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
buildtworzy katalogdist/i wysyła go jako artefakt, - job
testczeka nabuild(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.

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:
- zalogowanie się na serwer,
- pobranie artefaktu (albo aktualnego kodu z
main), - instalacja zależności na serwerze,
- 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:
- commit → pipeline CI (testy, build),
- merge do
main→ automatyczne wdrożenie na staging, - tag
vX.Y.Z→ pipeline produkcyjny, - 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 appModyfikacja 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 -rfPozostaje 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 1Gdy 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 environmentcieproduction.
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 zmain,.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
mainmusi 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.Zdo 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.






