GitHub Actions CI/CD for Monorepo Projects

Complete GitHub Actions CI/CD guide for Turborepo monorepos: affected-only builds, parallel jobs, caching strategies, environment-based deploys, and security best practices.

E
ECOSIRE Research and Development Team
|19 Mart 20268 dk okuma1.8k Kelime|

Monorepo Projeleri için GitHub Actions CI/CD

Bir monorepo için CI/CD işlem hattı, tek uygulamalı işlem hattından temel olarak farklı bir zorlukla karşı karşıyadır: 5 uygulamanızdan yalnızca 1'i değiştiğinde hızlı, güvenilir işlem hatlarını nasıl çalıştırırsınız? Her işlemin saf bir şekilde yapılması, 2 dakikalık bir boru hattını 15 dakikalık bir darboğaza dönüştürür ve geliştiriciler taahhütleri toplu olarak işleyerek bu sorunu çözmeye çalışırlar; tam da sizin engellemeye çalıştığınız şey budur.

Bu kılavuz, bir Turborepo monorepo için üretim GitHub Eylemleri kurulumunu kapsar: yalnızca etkilenen derlemeler, paralel iş yürütme, Turbo uzaktan önbelleğe alma entegrasyonu, ortam tabanlı dağıtım kapıları ve sırların günlüklere veya çatallı PR çalışmalarına sızmasını önleyen güvenlik modelleri.

Önemli Çıkarımlar

  • Turbo'nun etkilenen paket tespitini etkinleştirmek için ödeme sırasında fetch-depth: 2 kullanın
  • Paralel işleri çalıştırın, tip kontrolü yapın, test edin ve oluşturun; bunları sırayla zincirlemeyin
  • Matrix işleri, YAML'yi kopyalamadan birden fazla Node.js sürümü veya işletim sistemi arasında test yapmanıza olanak tanır
  • Turbo uzaktan önbellek kimlik bilgilerini YAML'deki ortam değişkenleri olarak değil, GitHub Gizli Dizileri olarak saklayın
  • Manuel onay gerektirmek için üretim dağıtım işlerinde environment: koruma kurallarını kullanın
  • paths-filter eylemi, yalnızca belgeler veya kod olmayan dosyalar değiştirildiğinde CI'yı tamamen atlamanıza olanak tanır
  • Gizli dizileri asla günlüklere yazdırmayın — çalışma zamanı tarafından oluşturulan gizli diziler için ::add-mask:: kullanın
  • Yeniden kullanılabilir iş akışları (workflow_call), birden çok veri havuzu iş akışında yinelemeleri ortadan kaldırır

CI için Depo Yapısı

İyi bir CI kurulumu, geliştirme iş akışınızı yansıtır. Turborepo monorepo için çalışan yapı şu şekildedir:

.github/
  workflows/
    ci.yml          — Runs on every PR and push to main
    deploy.yml      — Runs on push to main (after CI passes)
    security.yml    — Weekly security scans
    cleanup.yml     — Scheduled: delete stale preview deployments
  actions/
    setup-pnpm/
      action.yml    — Reusable: install Node + pnpm + cache

Temel CI İş Akışı

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]

# Cancel in-progress runs when a new run starts
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
  NODE_VERSION: '22'
  PNPM_VERSION: '10.28.0'

jobs:
  # Fast path: skip if only docs/assets changed
  changes:
    runs-on: ubuntu-latest
    outputs:
      code: ${{ steps.filter.outputs.code }}
      docs: ${{ steps.filter.outputs.docs }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            code:
              - 'apps/**/*.ts'
              - 'apps/**/*.tsx'
              - 'packages/**/*.ts'
              - 'turbo.json'
              - 'package.json'
              - 'pnpm-lock.yaml'
            docs:
              - 'apps/docs/**'
              - '**/*.md'

  lint:
    needs: changes
    if: needs.changes.outputs.code == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Required for turbo affected detection

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm turbo run lint

      - name: Type check
        run: pnpm turbo run type-check

  test:
    needs: changes
    if: needs.changes.outputs.code == 'true'
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17-alpine
        env:
          POSTGRES_DB: ecosire_test
          POSTGRES_USER: ecosire
          POSTGRES_PASSWORD: password
        ports:
          - 5433:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run unit tests
        run: pnpm turbo run test
        env:
          DATABASE_URL: postgresql://ecosire:password@localhost:5433/ecosire_test
          REDIS_URL: redis://localhost:6379

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./apps/api/coverage/lcov.info

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build all apps
        run: pnpm turbo run build
        env:
          NEXT_PUBLIC_API_URL: ${{ vars.STAGING_API_URL }}

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: |
            apps/web/.next/
            apps/api/dist/
          retention-days: 1  # Keep briefly for deploy job

E2E Oyun Yazarıyla Test İşi

  e2e:
    needs: build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        project: [chromium, firefox, mobile-chrome]
      fail-fast: false  # Don't cancel other browsers if one fails

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright browsers
        run: cd apps/web && npx playwright install ${{ matrix.project }} --with-deps

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-${{ github.sha }}

      - name: Start test servers
        run: |
          cd apps/api && node dist/main.js &
          cd apps/web && npx next start &
          npx wait-on http://localhost:3000 http://localhost:3001/health
        env:
          DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

      - name: Run E2E tests
        run: cd apps/web && npx playwright test --project=${{ matrix.project }}
        env:
          BASE_URL: http://localhost:3000

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report-${{ matrix.project }}
          path: apps/web/playwright-report/
          retention-days: 7

Güvenlik Taraması İş Akışı

# .github/workflows/security.yml
name: Security Scan

on:
  schedule:
    - cron: '0 2 * * 1'  # Weekly Monday 2am UTC
  push:
    paths:
      - '**/package.json'
      - 'pnpm-lock.yaml'

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

      - uses: pnpm/action-setup@v4
        with:
          version: '10.28.0'

      - name: Run pnpm audit
        run: pnpm audit --audit-level moderate
        continue-on-error: true  # Don't fail CI, just report

      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          command: test
          args: --severity-threshold=high

  secret-scanning:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for secret scanning

      - name: Run Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Dağıtım İş Akışı

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deploy environment'
        required: true
        default: 'production'
        type: choice
        options:
          - production
          - staging

# Only one production deploy at a time
concurrency:
  group: deploy-${{ inputs.environment }}
  cancel-in-progress: false  # Don't cancel in-flight deploys

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

      - uses: pnpm/action-setup@v4
        with:
          version: '10.28.0'

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile

      - name: Run quality checks
        run: |
          pnpm turbo run lint type-check test
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

  deploy:
    needs: quality-gate
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}  # Requires approval for production
    steps:
      - uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            set -e

            cd /opt/ecosire/app

            echo "Pulling latest changes..."
            git pull origin main

            echo "Installing dependencies..."
            pnpm install --frozen-lockfile

            echo "Building..."
            TURBO_TOKEN=${{ secrets.TURBO_TOKEN }} \
            TURBO_TEAM=${{ vars.TURBO_TEAM }} \
            npx turbo run build

            echo "Running migrations..."
            pnpm --filter @ecosire/db db:migrate

            echo "Restarting services..."
            pm2 restart ecosystem.config.cjs --update-env

            echo "Health check..."
            sleep 10
            curl -f https://ecosire.com/api/health || exit 1
            curl -f https://api.ecosire.com/api/health || exit 1

            echo "Deploy complete!"

      - name: Notify on success
        if: success()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.repos.createCommitComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              commit_sha: context.sha,
              body: `Deployed to ${process.env.ENVIRONMENT} at ${new Date().toISOString()}`
            })
        env:
          ENVIRONMENT: ${{ inputs.environment }}

      - name: Notify on failure
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `Deploy failed: ${process.env.ENVIRONMENT}`,
              body: `Deploy to ${process.env.ENVIRONMENT} failed at ${new Date().toISOString()}\n\nRun: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
            })
        env:
          ENVIRONMENT: ${{ inputs.environment }}

Önbelleğe Alma Stratejisi

Etkili önbelleğe alma, 2 dakikalık ve 8 dakikalık CI çalıştırmaları arasındaki farktır:

# Reusable setup step (extract to composite action)
# .github/actions/setup-pnpm/action.yml
name: Setup pnpm
runs:
  using: composite
  steps:
    - uses: pnpm/action-setup@v4
      with:
        version: '10.28.0'

    - uses: actions/setup-node@v4
      with:
        node-version: '22'
        cache: 'pnpm'

    # Turbo cache (separate from pnpm cache)
    - uses: actions/cache@v4
      with:
        path: .turbo
        key: turbo-${{ runner.os }}-${{ github.sha }}
        restore-keys: |
          turbo-${{ runner.os }}-

    - name: Install dependencies
      run: pnpm install --frozen-lockfile
      shell: bash

En İyi Güvenlik Uygulamaları

Asla sırları tekrarlamayın:

# Wrong — secret appears in logs
- run: echo "API key is ${{ secrets.API_KEY }}"

# If you must generate and use runtime secrets
- run: |
    echo "::add-mask::$GENERATED_SECRET"
    GENERATED_SECRET=$(generate-secret)
    echo "GENERATED_SECRET=$GENERATED_SECRET" >> $GITHUB_ENV

İzinleri kısıtla:

# Minimum permissions at workflow level
permissions:
  contents: read
  actions: read
  checks: write  # Add only what you need

jobs:
  test:
    permissions:
      contents: read  # Override at job level if needed

Çekme isteği enjeksiyonunu önleyin:

# For PRs from forks — never expose secrets
on:
  pull_request:

jobs:
  test:
    # Secrets not available in fork PRs by default
    # Use pull_request_target only for trusted actions
    runs-on: ubuntu-latest

Sıkça Sorulan Sorular

Bir iş akışını yalnızca belirli paketler değiştiğinde nasıl tetikleyebilirim?

Hangi paketlerin değiştiğini tespit etmek için dorny/paths-filter kullanın, ardından filtre çıktılarını iş koşullarına aktarın. Turbo'nun yerleşik etkilenen algılaması için ödeme adımınızda fetch-depth: 2 olduğundan emin olun, böylece Turbo önceki işlemeyle karşılaştırabilir. Yalnızca değiştirilen paketler ve bunların bağımlıları için testler çalıştırmak için pnpm turbo run test --filter="...[HEAD^1]" kullanın.

CI'da veritabanı geçişlerini nasıl ele almalıyım?

Geçişleri, testlerden sonra ancak son yapı yapıtı oluşturulmadan önce çalıştırılan ayrı bir işte çalıştırın. CI'da özel bir test veritabanı URL'si kullanın (geliştirme ve üretimden ayrı). Geçişleri asla CI'dan üretime karşı otomatik olarak çalıştırmayın; üretim veritabanı değişiklikleri için GitHub ortam koruma kuralları aracılığıyla manuel onayla workflow_dispatch kullanın.

Birden fazla ortamda gizli bilgileri yönetmenin en iyi yolu nedir?

GitHub Ortamlarını (Ayarlar > Ortamlar) koruma kurallarıyla kullanın. staging ve production için ayrı ortamlar oluşturun. Ortama özgü gizli dizileri depo düzeyinde değil, her ortamın altında saklayın. Paylaşılan gizli diziler için (Turbo uzaktan önbellek belirteci gibi), depo düzeyinde gizli dizileri kullanın. İş yapılandırmasındaki environment: anahtarı, ortama özgü gizli dizileri ve koruma kurallarını etkinleştirir.

CI'da pnpm kurulumunu nasıl hızlandırabilirim?

actions/setup-node@v4 ile cache: 'pnpm' pnpm deposunu önbelleğe alır. Büyük monorepolar için önbelleği geri yükledikten sonra pnpm install --prefer-offline öğesini de kullanın. Pnpm mağaza önbelleğe alma ve Turbo uzaktan önbelleğe alma kombinasyonu, önbellek isabetlerinde genellikle kurulum + derleme süresini %60-80 oranında azaltır.

PR'ler ve ana şube gönderimleri için farklı iş akışlarını nasıl çalıştırırım?

on tetikleme koşullarını kullanın: ana için on.push.branches ve PR'ler için on.pull_request.branches. PR'ler (lint, birim testleri) ve tam paket için daha hızlı bir test alt kümesini main üzerinde çalıştırabilirsiniz. Dağıtım işlerini PR olaylarında değil, yalnızca ana aktarımda çalıştırmak için github.event_name == 'push' koşullarını kullanın.


Sonraki Adımlar

İyi tasarlanmış bir CI/CD işlem hattı, yüksek hızlı bir mühendislik ekibinin temelidir; sık sık gönderim yapma ve gerilemeleri otomatik olarak yakalama konusunda size güven verir. ECOSIRE, GitHub Actions CI'yı lint, type-check, 1.301 birim testi, Playwright E2E ve ana sürüme her geçişte sıfır kesinti süreli SSH dağıtımlarıyla çalıştırır.

İster CI/CD mimarisi danışmanlığına, ister Turborepo monorepo kurulumuna, ister eksiksiz DevOps desteğine ihtiyacınız olsun, mühendislik hizmetlerimizi keşfedin.

E

Yazan

ECOSIRE Research and Development Team

ECOSIRE'da kurumsal düzeyde dijital ürünler geliştiriyor. Odoo entegrasyonları, e-ticaret otomasyonu ve yapay zeka destekli iş çözümleri hakkında içgörüler paylaşıyor.

WhatsApp'ta Sohbet Et