GitHub Actions. Mój CI CD dla Next.js plus Strapi w 100 liniach YAML
Pierwszy CI/CD, który skonfigurowałem, to był Jenkins w 2019. Trzy tygodnie setupu, custom serwer, plugin hell. Po każdej aktualizacji coś padało. Kiedy w końcu działało, deploy zajmował 20 minut.
W 2020 przeszedłem na GitHub Actions. Setup: 15 minut. Deploy pierwszego projektu: 4 minuty. Zero infrastruktury do utrzymania.
Dziś każdy projekt w agencji ma GitHub Actions. Pipeline’y build, test, deploy, monitoring. Wszystko definiowane w YAML w tym samym repo co kod. Zero mental overhead.
Ten post pokaże Ci mój dokładny setup dla stacka Next.js + Strapi + Postgres w monorepo. Konkretne pliki, konkretne komendy, dlaczego robimy tak, a nie inaczej.
Co to jest GitHub Actions
GitHub Actions to CI/CD wbudowany w GitHub. Pipeline’y definiujesz jako YAML w .github/workflows/. Trigger’y (push, PR, cron, manual) uruchamiają workflow’y.
Free tier: 2000 minut miesięcznie dla private repos. Public repos = unlimited. Dla większości małych i średnich projektów bezpłatne.
Ekosystem: dziesiątki tysięcy prebuilt actions (deploy do AWS, notify Slack, run tests). Composability jest killer feature.
Dlaczego GitHub Actions, nie inne
CircleCI. Klasyk, świetne performance. Ale drogie, wymaga osobnego konta.
GitLab CI. Świetne, jeśli używasz GitLab. Bezsensowne, jeśli kod jest na GitHub.
Jenkins. Klasyk klasyków. Self-hosted, ogromna flexibility, ale utrzymanie to full-time job.
CircleCI, Travis, Drone. Wszystkie działają, ale mniejszy ekosystem, więcej frykcji.
Vercel/Netlify built-in. Dobre dla stricte frontendowych apps. Ale nie do backendu, testów, complex flows.
Dla większości projektów w 2026 GitHub Actions to default. Kod na GitHub → CI/CD na GitHub. Zero switching, zero extra kont.
Podstawowa struktura
Workflow files w .github/workflows/:
.github/
└── workflows/
├── ci.yml # Tests + linting na każdy PR
├── deploy-web.yml # Deploy Next.js
├── deploy-cms.yml # Deploy Strapi
├── security.yml # Scan raz w tygodniu
└── cleanup.yml # Delete stare artefakty raz w miesiącu
Każdy plik = jeden workflow. Może mieć wiele jobs. Każdy job ma wiele steps.
Mój CI workflow
Pierwszy, najważniejszy. Uruchamiany na każdym PR i push do main.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
concurrency:
group: $-$
cancel-in-progress: true
jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Cache turbo
uses: actions/cache@v4
with:
path: .turbo
key: turbo-$
restore-keys: |
turbo-
lint:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
typecheck:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
test:
needs: install
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
env:
DATABASE_URL: postgresql://postgres:test@localhost:5432/test
build:
needs: [lint, typecheck, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
Kluczowe rzeczy tutaj:
- Concurrency: nowe pushe kancelują stare buildy. Oszczędność minut.
- Parallel jobs: lint, typecheck, test lecą równolegle. Szybciej.
- Postgres service: baza testowa uruchamiana w kontenerze dla testów.
- Turbo cache: monorepo z TurboRepo cache’uje buildy między runami.
Deploy Next.js na Vercel
Vercel ma dedicated GitHub App - deploy jest automatyczny bez własnego workflow. Ale kiedy chcesz custom flow (feature flags, notifications), używasz Actions.
# .github/workflows/deploy-web.yml
name: Deploy Web
on:
push:
branches: [main]
paths:
- 'apps/web/**'
- 'packages/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vercel CLI
run: npm install --global vercel@latest
- name: Pull Vercel Environment
run: vercel pull --yes --environment=production --token=$
- name: Build
run: vercel build --prod --token=$
- name: Deploy
run: vercel deploy --prebuilt --prod --token=$
- name: Notify Slack
if: success()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "🚀 Web deployed to production by $"
}
env:
SLACK_WEBHOOK_URL: $
- name: Notify Sentry
uses: getsentry/action-release@v1
env:
SENTRY_AUTH_TOKEN: $
SENTRY_ORG: my-org
SENTRY_PROJECT: my-app
with:
environment: production
Kluczowe:
- Path filter: workflow uruchamia się tylko, gdy zmiany w
apps/web/lubpackages/. Bez tego każdy commit trigger’uje pełny build. - Vercel CLI: zamiast git push, controlowany deploy przez CLI.
- Notifications: Slack po deploy, Sentry o nowej wersji.
Deploy Strapi na własny serwer
Strapi zwykle nie idzie na Vercela (nie wspiera long-running processes). Deploy przez Docker + SSH.
# .github/workflows/deploy-cms.yml
name: Deploy CMS
on:
push:
branches: [main]
paths:
- 'apps/cms/**'
- 'packages/**'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: $
password: $
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: apps/cms/Dockerfile
push: true
tags: |
ghcr.io/$/cms:latest
ghcr.io/$/cms:$
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: SSH deploy
uses: appleboy/ssh-action@v1
with:
host: $
username: deploy
key: $
script: |
cd /var/apps/cms
docker pull ghcr.io/$/cms:latest
docker compose up -d --no-deps cms
docker system prune -f
Flow:
- Build image z Dockera
- Push do GHCR (GitHub Container Registry, free)
- SSH do serwera
- Pull nowej wersji, restart kontenera
- Cleanup starych images
Cały flow: 5-8 minut. Automatyczne, powtarzalne.
Cache: gdzie kończy się większość minut
Bez cache buildy są wolne. Z cache błyskawiczne.
Node modules cache:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
Setup-node action automatycznie cache’uje. Pierwszy run: 60 sekund pnpm install. Następne: 5 sekund.
Turbo cache:
- name: Cache turbo
uses: actions/cache@v4
with:
path: .turbo
key: turbo-$
restore-keys: |
turbo-
Turbo build z cache: 2 sekundy dla niezmienionych paczek. Bez cache: 45 sekund.
Docker layer cache:
cache-from: type=gha
cache-to: type=gha,mode=max
Layers Docker cache’owane w GitHub Actions storage. Kolejne buildy 3-5x szybsze.
Bez cache ważne pipeline’y potrafią zajmować 10-15 minut. Z cache 2-4 minuty. Różnica w free tier minutes: 3-4x.
Secrets management
Sekrety (API keys, tokens, passwords) nigdy w repo. GitHub Actions Secrets:
Repo → Settings → Secrets and Variables → Actions → New repository secret.
Używanie:
env:
DATABASE_URL: $
Organization secrets dla wielu repo. Environment secrets dla różnych environmentów (staging vs prod, różne wartości).
Nie kompromituj się:
- Nie print’uj secretów w logach
- Nie commituj
.env(add do.gitignore) - Rotate secrety co 3-6 miesięcy
- Delete deprecated secrety
Matrix builds
Testowanie na wielu środowiskach naraz:
strategy:
matrix:
node: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
To uruchomi test 6 razy (3 node × 2 OS). Świetnie do libraries. Overkill dla aplikacji.
Dla mojego stacka (dedykowany node 20 na Ubuntu w prod) matrix nie potrzebny.
Feature flags przez branchowanie
Chcesz deploy tylko dla części userów?
Ephemeral environments: każdy PR ma własny preview URL na Vercelu. Testujesz zmiany przed merge.
Staging: develop branch → staging environment. Prod-like data, real testing.
Production: main branch → prod. Only after staging approval.
Konfiguracja:
on:
push:
branches:
- main # → production
- develop # → staging
pull_request: # → preview (Vercel auto)
Trzy environmenty, trzy różne workflow triggers.
Cost management
GitHub Actions free tier:
- Public repos: unlimited
- Private repos: 2000 minutes/month (Free), 3000 (Team), 50k (Enterprise)
Naive setup potrafi zjeść 2000 minut w tydzień na aktywnym projekcie.
Techniki oszczędzania:
Concurrency cancellation: nowe pushe kancelują stare. Save 30-50% minut.
Path filters: builduj tylko to, co się zmieniło.
Cache. Cache. Cache.
Self-hosted runners: własny serwer jako runner. Minuty nie liczone. Setup: 30 minut. Wybór dla dużych zespołów.
Skip CI w commitach WIP: [skip ci] w commit message pomija workflow.
Monitoring pipeline’ów
Twój pipeline pada? Bez notyfikacji nie wiesz.
GitHub notifications: email/mobile push na failed builds. Włącz w settings.
Slack integration: notyfikacje w team channel. Konkretny format:
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ Build failed in $ by $\n$"
}
Datadog / New Relic: metrics na build times, success rates. Dla zespołów, gdzie CI/CD to first-class citizen.
Security best practices
Pin action versions:
Złe: uses: actions/checkout@v4 (tag, może się zmienić).
Lepsze: uses: actions/checkout@a1b2c3... (commit SHA, immutable).
Attacker, który przejmuje action, nie wpłynie na Twoje buildy.
Minimal permissions:
permissions:
contents: read
packages: write
Nie przyznawaj więcej, niż potrzeba.
Restricted workflows: pull_request_target niebezpieczne (dostęp do secretów z PR forks). Używaj pull_request zamiast, jeśli nie musisz koniecznie.
Dependabot: automatyczne update actions. Włącz w repo settings.
Częste błędy
Brak concurrency cancellation. Push 3 razy w minucie, 3 buildy leci równolegle. Cost, wait time. Dodaj concurrency block.
Buildowanie wszystkiego naraz. Push do frontend triggeruje build backendu. Marnujesz minuty. Path filters.
Testy bez isolated env. Testy używają real DB. Testy modyfikują state. Kolejny test nie działa. Postgres service w Compose dla każdego job’a.
Ignorowanie failed tests. continue-on-error: true dla całego pipeline. Failed testy nikogo nie obchodzą. Fix testy albo delete.
Deploy bez rollback strategy. Deploy padnie na produkcji, brak sposobu na powrót do poprzedniej wersji. Docker images taged with SHA, docker compose up z tag = rollback.
Nie test’owanie workflow lokalnie. Push to GitHub, czekasz 5 minut, dowiedujesz się, że YAML jest zły. act (open source) uruchamia workflow lokalnie.
Duplicate installation steps. Każdy job powtarza checkout, setup-node, install. Composite actions albo reusable workflows redukują boilerplate.
Reusable workflows
Jeśli masz 5 workflow’ów z tym samym setupem:
# .github/workflows/reusable-setup.yml
name: Setup
on:
workflow_call:
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
- run: pnpm install --frozen-lockfile
Wywoływanie:
jobs:
setup:
uses: ./.github/workflows/reusable-setup.yml
test:
needs: setup
...
DRY dla CI. Change w jednym miejscu propaguje wszędzie.
Co czytać dalej
- GitHub Actions docs. Najlepsze dokumentacja CI/CD w branży.
- awesome-actions na GitHubie: curated lista prebuilt actions.
- Julien Renaux blog: praktyczne case studies na Actions.
Nie kupuj kursów. Dokumentacja + 3 real workflows = wystarczy.
Od czego zacząć
Jeśli nie masz jeszcze CI/CD:
- Stwórz
.github/workflows/ci.ymlz basic testami. - Dodaj lint i typecheck.
- Włącz protected branches w GitHub settings (wymóg passed CI przed merge).
- Po tygodniu dodaj deploy workflow.
- Po miesiącu dodaj cache, concurrency, path filters.
- Po 3 miesiącach rozważ reusable workflows, jeśli masz dużo pipeline’ów.
GitHub Actions to unfair advantage w 2026. Zero infrastruktury do utrzymania, potężny ekosystem, dobra integracja z resztą GitHub.
Pisałem o mojej stack agencji w Docker, Next.js, TurboRepo. GitHub Actions to klej, który spina te elementy w automatyczny deployment flow.
3 godziny setupu, save 3-5 godzin tygodniowo na manualnym deployowaniu. ROI liczony w tygodniach.
Powiązane wpisy
-
Playwright vs Cypress. Testowanie E2E po roku z każdym w produkcji
W 2023 mieliśmy w agencji trzy projekty z Cypress. Wszystkie miały ten sam problem: flaky testy, wolne CI, dev'y nie ...
-
Vitest. Dlaczego przeszedłem z Jest i co się zmieniło w produktywności testów
Jest był standardem od 2016. Napisałem w nim tysiące testów. Był dobry na swój czas. W 2026 nie jest dobry na dziś. ...