CI/CD z GitHub Actions dla Next.js i .NET - Kompletny przewodnik
CI/CD GitHub Actions
devopsCI/CD z GitHub Actions dla Next.js i .NET
Automatyzacja procesu budowania, testowania i wdrażania aplikacji to fundament nowoczesnego wytwarzania oprogramowania. GitHub Actions, wbudowane bezpośrednio w platformę GitHub, umożliwia tworzenie zaawansowanych pipeline'ów CI/CD bez konieczności konfigurowania zewnętrznych narzędzi. W tym przewodniku pokażemy, jak skonfigurować kompletne workflow dla projektów Next.js i .NET — od pierwszego commita po wdrożenie produkcyjne.
Czym jest CI/CD?#
Continuous Integration (CI) to praktyka polegająca na częstym łączeniu zmian kodu do głównej gałęzi repozytorium. Każda zmiana jest automatycznie weryfikowana przez zautomatyzowane testy i procesy budowania, co pozwala na wczesne wykrycie błędów.
Continuous Delivery / Continuous Deployment (CD) to rozszerzenie CI o automatyczne wdrażanie aplikacji. Continuous Delivery oznacza, że każda zmiana, która przejdzie testy, jest gotowa do wdrożenia (ale wymaga ręcznego zatwierdzenia). Continuous Deployment idzie o krok dalej — automatycznie wdraża każdą zmianę, która przejdzie pipeline.
Korzyści z wdrożenia CI/CD:
- Szybsze wykrywanie błędów — problemy są identyfikowane natychmiast po commicie
- Krótszy czas dostarczania — od commita do produkcji w minutach
- Wyższa jakość kodu — automatyczne testy i linting przy każdej zmianie
- Powtarzalność — każde wdrożenie przebiega identycznie
- Mniejsze ryzyko — małe, częste zmiany zamiast dużych, ryzykownych wdrożeń
GitHub Actions — przegląd platformy#
GitHub Actions to platforma CI/CD zintegrowana z GitHub. Oferuje:
- Darmowe minuty dla repozytoriów publicznych (nielimitowane) i prywatnych (2000 min/mies. w planie Free)
- Hosted runners — maszyny wirtualne z Linux, Windows i macOS
- Self-hosted runners — możliwość uruchomienia na własnej infrastrukturze
- Marketplace — tysiące gotowych akcji stworzonych przez społeczność
- Integracja z GitHub — natywne wsparcie dla pull requestów, issues i deploymentów
Składnia workflow — anatomia pliku YAML#
Każdy workflow GitHub Actions to plik YAML umieszczony w katalogu .github/workflows/. Oto kluczowe elementy składni:
# .github/workflows/ci.yml
name: CI Pipeline # Nazwa workflow
on: # Triggery (wyzwalacze)
push:
branches: [main, develop] # Uruchom przy push do main/develop
pull_request:
branches: [main] # Uruchom przy PR do main
workflow_dispatch: # Pozwól na ręczne uruchomienie
env: # Zmienne środowiskowe (globalne)
NODE_VERSION: "20"
DOTNET_VERSION: "8.0.x"
jobs: # Definicje zadań
build: # Nazwa zadania
runs-on: ubuntu-latest # Runner
timeout-minutes: 15 # Timeout
steps: # Kroki zadania
- name: Checkout code
uses: actions/checkout@v4 # Użyj gotowej akcji
- name: Run custom script
run: echo "Hello CI/CD!" # Uruchom komendę shell
Triggery (wyzwalacze)#
GitHub Actions obsługuje wiele typów triggerów:
on:
push:
branches: [main, develop]
paths:
- "src/**" # Tylko gdy zmienią się pliki w src/
- "!docs/**" # Ignoruj zmiany w docs/
tags:
- "v*" # Uruchom przy tagach v1.0, v2.1 itd.
pull_request:
types: [opened, synchronize, reopened]
branches: [main]
schedule:
- cron: "0 6 * * 1" # Co poniedziałek o 6:00 UTC
workflow_dispatch: # Ręczne uruchomienie
inputs:
environment:
description: "Target environment"
required: true
default: "staging"
type: choice
options:
- staging
- production
Jobs i Steps#
Zadania (jobs) działają domyślnie równolegle. Możesz definiować zależności między nimi:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
test:
runs-on: ubuntu-latest
needs: lint # Uruchom po zakończeniu lint
steps:
- uses: actions/checkout@v4
- run: npm test
deploy:
runs-on: ubuntu-latest
needs: [lint, test] # Uruchom po lint AND test
if: github.ref == 'refs/heads/main' # Tylko na branchu main
steps:
- run: echo "Deploying..."
Workflow CI/CD dla Next.js#
Poniższy workflow obejmuje pełny pipeline dla aplikacji Next.js — od lintingu po wdrożenie na Vercel:
# .github/workflows/nextjs-ci-cd.yml
name: Next.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: "20"
jobs:
# ─── Etap 1: Lint ───────────────────────────
lint:
name: Lint & Format Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Check Prettier formatting
run: npx prettier --check "src/**/*.{ts,tsx,js,jsx}"
- name: TypeScript type check
run: npx tsc --noEmit
# ─── Etap 2: Testy ──────────────────────────
test:
name: Unit & Integration Tests
runs-on: ubuntu-latest
needs: lint
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test -- --coverage --ci
env:
CI: true
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7
# ─── Etap 3: Build ──────────────────────────
build:
name: Build Application
runs-on: ubuntu-latest
needs: test
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build Next.js application
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: nextjs-build
path: .next/
retention-days: 1
# ─── Etap 4: Deploy na Vercel ───────────────
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/develop'
environment:
name: staging
url: ${{ steps.deploy.outputs.url }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to Vercel (Preview)
id: deploy
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
scope: ${{ secrets.VERCEL_ORG_ID }}
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to Vercel (Production)
id: deploy
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--prod"
scope: ${{ secrets.VERCEL_ORG_ID }}
Workflow CI/CD dla .NET#
Oto kompletny pipeline dla aplikacji .NET, obejmujący budowanie, testowanie i publikację:
# .github/workflows/dotnet-ci-cd.yml
name: .NET CI/CD
on:
push:
branches: [main, develop]
paths:
- "src/**"
- "tests/**"
- "*.sln"
- "*.csproj"
pull_request:
branches: [main]
env:
DOTNET_VERSION: "8.0.x"
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
SOLUTION_PATH: "./MyApp.sln"
jobs:
# ─── Etap 1: Build i Test ───────────────────
build-and-test:
name: Build & Test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore NuGet packages
run: dotnet restore ${{ env.SOLUTION_PATH }}
- name: Build solution
run: dotnet build ${{ env.SOLUTION_PATH }} --configuration Release --no-restore
- name: Run unit tests
run: |
dotnet test ${{ env.SOLUTION_PATH }} \
--configuration Release \
--no-build \
--verbosity normal \
--collect:"XPlat Code Coverage" \
--results-directory ./test-results
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: ./test-results/
retention-days: 7
# ─── Etap 2: Analiza kodu ───────────────────
code-analysis:
name: Code Analysis
runs-on: ubuntu-latest
needs: build-and-test
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Run dotnet format check
run: dotnet format ${{ env.SOLUTION_PATH }} --verify-no-changes --verbosity diagnostic
# ─── Etap 3: Publish ────────────────────────
publish:
name: Publish Application
runs-on: ubuntu-latest
needs: [build-and-test, code-analysis]
if: github.ref == 'refs/heads/main'
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Publish application
run: |
dotnet publish src/MyApp.Api/MyApp.Api.csproj \
--configuration Release \
--output ./publish \
--self-contained false \
--runtime linux-x64
- name: Upload published artifacts
uses: actions/upload-artifact@v4
with:
name: dotnet-publish
path: ./publish/
retention-days: 5
Docker Build and Push#
Budowanie i publikowanie obrazów Docker to kluczowy element pipeline'u CD. Oto workflow, który buduje obraz i wypycha go do GitHub Container Registry (GHCR):
# .github/workflows/docker-build.yml
name: Docker Build & Push
on:
push:
branches: [main]
tags: ["v*.*.*"]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_VERSION=${{ github.sha }}
Cachowanie zależności#
Prawidłowe cachowanie może skrócić czas wykonania pipeline'u nawet o 70%. Oto strategie dla różnych technologii:
Cache dla Node.js / Next.js#
- name: Setup Node.js with cache
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm" # Automatyczny cache node_modules
# Lub ręczne cachowanie dla większej kontroli:
- name: Cache Node modules
uses: actions/cache@v4
id: npm-cache
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
${{ runner.os }}-nextjs-
Cache dla .NET#
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
Cache dla Docker#
- name: Build with GitHub Actions cache
uses: docker/build-push-action@v5
with:
context: .
cache-from: type=gha
cache-to: type=gha,mode=max
Zmienne środowiskowe i sekrety#
GitHub Actions oferuje kilka poziomów zarządzania zmiennymi i sekretami:
Poziomy konfiguracji#
# Poziom workflow (dostępne we wszystkich jobsach)
env:
APP_NAME: "my-application"
jobs:
deploy:
# Poziom joba
env:
DEPLOY_ENV: "staging"
steps:
- name: Build
# Poziom stepu
env:
API_KEY: ${{ secrets.API_KEY }}
run: echo "Building for $APP_NAME in $DEPLOY_ENV"
Environments (środowiska)#
Environments w GitHub pozwalają na definiowanie sekretów i reguł ochrony per środowisko:
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} # Sekret ze środowiska staging
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
needs: deploy-staging
steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} # Sekret ze środowiska production
run: ./deploy.sh
Strategie wdrożeń — staging i produkcja#
Dobrze zaprojektowany pipeline powinien obsługiwać wiele środowisk z odpowiednimi bramkami bezpieczeństwa:
# .github/workflows/deploy.yml
name: Deploy Pipeline
on:
push:
branches: [main, develop]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
deploy-staging:
name: Deploy to Staging
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment:
name: staging
url: https://staging.myapp.com
steps:
- uses: actions/checkout@v4
- name: Deploy to staging server
run: |
echo "Deploying to staging..."
# Komenda wdrożenia na staging
env:
DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
deploy-production:
name: Deploy to Production
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production # Wymaga ręcznego zatwierdzenia (konfiguracja w repo settings)
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying to production..."
# Komenda wdrożenia na produkcję
env:
DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }}
- name: Notify on success
if: success()
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d '{"text":"Deployment to production successful! Commit: ${{ github.sha }}"}'
Branch Protection Rules i Status Checks#
Ochrona gałęzi to krytyczny element bezpieczeństwa pipeline'u. Konfiguracja w ustawieniach repozytorium (Settings > Branches):
Zalecane reguły dla gałęzi main:
- Require pull request reviews — wymagaj przynajmniej 1-2 recenzentów
- Require status checks to pass — zaznacz workflow CI jako wymagany
- Require branches to be up to date — wymuszaj rebase/merge z main przed mergem
- Require signed commits — opcjonalnie, dla dodatkowego bezpieczeństwa
- Do not allow bypassing — nawet administratorzy muszą przestrzegać reguł
Status checks integrują się bezpośrednio z pull requestami:
# Ten job pojawi się jako wymagany status check
jobs:
ci:
name: "CI / Build & Test" # Ta nazwa pojawi się w PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
Reusable Workflows — wielokrotne użycie#
Zamiast kopiować konfigurację między repozytoriami, twórz reusable workflows:
# .github/workflows/reusable-nextjs-ci.yml
name: Reusable Next.js CI
on:
workflow_call: # Pozwól na wywołanie z innych workflow
inputs:
node-version:
description: "Node.js version"
required: false
default: "20"
type: string
run-e2e:
description: "Run E2E tests"
required: false
default: false
type: boolean
secrets:
VERCEL_TOKEN:
required: false
SONAR_TOKEN:
required: false
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: "npm"
- run: npm ci
- run: npm run lint
- run: npm test -- --ci
- name: E2E Tests
if: inputs.run-e2e
run: npx playwright test
Wywołanie reusable workflow:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
nextjs-ci:
uses: ./.github/workflows/reusable-nextjs-ci.yml
with:
node-version: "20"
run-e2e: true
secrets:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
Matrix Builds — testowanie na wielu konfiguracjach#
Strategia matrix pozwala uruchamiać ten sam job na wielu konfiguracjach jednocześnie:
jobs:
test:
name: Test on Node ${{ matrix.node-version }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # Nie przerywaj innych jobów po porażce jednego
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
exclude:
- os: windows-latest
node-version: 18 # Pomiń tę kombinację
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
test-dotnet:
name: .NET Test on ${{ matrix.dotnet-version }}
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: ["7.0.x", "8.0.x"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- run: dotnet test --configuration Release
Kompletny pipeline — Next.js + .NET w jednym repozytorium#
Jeśli Twój projekt to fullstack z Next.js (frontend) i .NET (backend API), oto połączony workflow:
# .github/workflows/fullstack-ci-cd.yml
name: Fullstack CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# ─── Frontend (Next.js) ─────────────────────
frontend-ci:
name: Frontend CI
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm test -- --ci
- run: npm run build
# ─── Backend (.NET) ─────────────────────────
backend-ci:
name: Backend CI
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- run: dotnet restore
- run: dotnet build --configuration Release --no-restore
- run: dotnet test --configuration Release --no-build
# ─── Docker Build ───────────────────────────
docker-build:
name: Docker Build
needs: [frontend-ci, backend-ci]
runs-on: ubuntu-latest
if: github.event_name == 'push'
strategy:
matrix:
include:
- context: ./frontend
image: frontend
dockerfile: ./frontend/Dockerfile
- context: ./backend
image: backend
dockerfile: ./backend/Dockerfile
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
push: true
tags: ghcr.io/${{ github.repository }}/${{ matrix.image }}:${{ github.sha }}
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,scope=${{ matrix.image }},mode=max
# ─── Deploy ─────────────────────────────────
deploy:
name: Deploy
needs: docker-build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
run: |
echo "Deploying frontend and backend..."
# kubectl set image deployment/frontend ...
# kubectl set image deployment/backend ...
Najlepsze praktyki#
- Używaj konkretnych wersji akcji —
actions/checkout@v4zamiastactions/checkout@main - Ogranicz uprawnienia — ustawiaj
permissionsna minimalny wymagany poziom - Cachuj zależności —
actions/cachelub wbudowane cachowanie wsetup-node/setup-dotnet - Ustawiaj timeouty —
timeout-minuteszapobiegnie zawieszonym jobom - Filtruj paths — uruchamiaj workflow tylko gdy zmienią się relevantne pliki
- Używaj environments — oddzielne sekrety dla staging i production
- Monitoruj czas wykonania — optymalizuj pipeline, gdy przekracza 10 minut
- Wersjonuj workflow — traktuj pliki YAML jak kod produkcyjny
Podsumowanie#
GitHub Actions to potężna platforma CI/CD, która w połączeniu z Next.js i .NET pozwala na stworzenie kompletnego, zautomatyzowanego pipeline'u od commita po wdrożenie produkcyjne. Kluczowe elementy to odpowiednie cachowanie, strategie wdrożeń z podziałem na środowiska, ochrona gałęzi i reusable workflows.
Wdrożenie dobrze zaprojektowanego pipeline'u CI/CD to inwestycja, która zwraca się wielokrotnie — w postaci szybszych wdrożeń, wyższej jakości kodu i spokojniejszego snu zespołu deweloperskiego.
Potrzebujesz profesjonalnej konfiguracji CI/CD dla swojego projektu? W MDS Software Solutions Group projektujemy i wdrażamy zaawansowane pipeline'y CI/CD dla aplikacji Next.js, .NET i nie tylko. Od prostych konfiguracji po złożone, wielośrodowiskowe wdrożenia z Docker i Kubernetes — pomożemy Ci zautomatyzować cały proces dostarczania oprogramowania. Skontaktuj się z nami, aby omówić swoje potrzeby DevOps.
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.