← Kursa Dön
📄 Text · 30 min

Workflow Yazma

Giriş — Workflow'un Gücünü Keşfet

İlk workflow'unu yazdın: push gelince lint, test, build çalışıyor. Güzel. Ama gerçek dünya daha karmaşık. "PR açıldığında farklı şeyler çalışsın", "her gece otomatik güvenlik taraması yapsın", "API key'i güvenli şekilde pipeline'a vereyim", "hem Node 18 hem Node 20'de test edeyim", "npm install her seferinde 2 dakika sürmesin" — bu ihtiyaçların her biri GitHub Actions'ın ileri özelliklerine çıkarıyor.

Bu derste workflow'ların gerçek potansiyelini açığa çıkaracağız: tetikleyicileri (triggers) derinlemesine öğrenecek, ortam değişkenleri ve secret'larla çalışacak, matrix strategy ile paralel test matrisleri kuracak ve caching ile pipeline süresini dramatik şekilde düşüreceksin.


🎬 Analoji: Akıllı Ev Otomasyonu

Workflow tetikleyicilerini bir akıllı ev sistemine benzetelim:

  • Push tetikleyici = Kapıdan girince ışıklar yansın

  • PR tetikleyici = Zil çalınca kamera açılsın

  • Schedule tetikleyici = Her gece 23:00'te kapılar kilitlensin

  • Manual tetikleyici = Telefondaki butona basınca müzik çalsın

  • Webhook tetikleyici = Hava 0°C'nin altına düşünce ısıtma açılsın

Her tetikleyici farklı bir olaya tepki verir. Doğru tetikleyiciyi seçmek, workflow'unun ne zaman ve neden çalışacağını belirler.

┌──────────────────────────────────────────────────────────────┐
│                   WORKFLOW TETİKLEYİCİLERİ                    │
│                                                              │
│  🔔 push          → Kod push edildiğinde                     │
│  🔔 pull_request  → PR açıldığında/güncellendiğinde          │
│  ⏰ schedule      → Belirli zamanlarda (cron)                │
│  🖱️ workflow_dispatch → Manuel tetikleme (UI'dan)            │
│  🔗 repository_dispatch → Dış API'den tetikleme             │
│  🏷️ release       → Release yayınlandığında                 │
│  📋 issues        → Issue açıldığında                        │
│  🔄 workflow_run  → Başka workflow bittiğinde                │
└──────────────────────────────────────────────────────────────┘

Tetikleyiciler (Triggers) Derinlemesine

push — Kod Push Edildiğinde

# Temel kullanım: Her push'ta
on: push

# Branch filtresi:
on:
  push:
    branches:
      - main
      - develop
      - 'release/**'      # release/1.0, release/2.0, ...
      - '!release/beta-*'  # release/beta-* hariç

# Tag filtresi:
on:
  push:
    tags:
      - 'v*'              # v1.0, v2.3.1, ...

# Path filtresi — sadece belirli dosyalar değiştiğinde:
on:
  push:
    paths:
      - 'src/**'           # src/ altı değiştiğinde
      - 'package.json'     # package.json değiştiğinde
    paths-ignore:
      - '**.md'            # Markdown değişiklikleri atla
      - 'docs/**'          # docs/ değişiklikleri atla

💡 İpucu: paths ve paths-ignore birlikte kullanılmaz. Birisini seç. Genel kural: Az sayıda dosyayı dahil etmek istiyorsan paths, az sayıda dosyayı hariç tutmak istiyorsan paths-ignore kullan.

pull_request — PR Olayları

on:
  pull_request:
    branches: [main]
    types:
      - opened         # PR yeni açıldı
      - synchronize    # PR'a yeni commit push edildi
      - reopened       # Kapatılıp tekrar açıldı
      # Varsayılan: [opened, synchronize, reopened]

    paths:
      - 'src/**'       # Sadece src/ değiştiğinde çalış
# PR ve push'u birlikte kullanmak (en yaygın pattern):
on:
  push:
    branches: [main]           # main'e merge olunca
  pull_request:
    branches: [main]           # main'e PR açılınca
push vs pull_request farkı:

push:
  → Branch'e doğrudan push yapıldığında
  → Merge commit push edildiğinde
  → Workflow, push yapan kişinin izinleriyle çalışır

pull_request:
  → PR açıldığında veya güncellendiğinde
  → Fork'lardan gelen PR'larda GÜVENLİ: secret'lara erişim yok
  → Workflow, PR'ın merge preview'ında çalışır

⚠️ Dikkat: Fork'lardan gelen pull_request event'lerinde secrets erişimi kapalıdır. Bu bir güvenlik önlemidir — rastgele birinin fork'undan gelen workflow kodun secret'larını çalabilir. pull_request_target bunu açar ama çok dikkatli kullanılmalı.

schedule — Zamanlanmış Çalıştırma

Cron sözdizimi ile belirli zamanlarda otomatik çalıştırma:

on:
  schedule:
    # ┌───────────── dakika (0-59)
    # │ ┌───────────── saat (0-23)
    # │ │ ┌───────────── ayın günü (1-31)
    # │ │ │ ┌───────────── ay (1-12)
    # │ │ │ │ ┌───────────── haftanın günü (0-6, Pazar=0)
    # │ │ │ │ │
    # * * * * *
    - cron: '0 2 * * *'       # Her gün saat 02:00 UTC
    - cron: '30 9 * * 1-5'    # Hafta içi her gün 09:30 UTC
    - cron: '0 0 1 * *'       # Her ayın 1'i gece yarısı
    - cron: '0 */6 * * *'     # Her 6 saatte bir
Yaygın Cron Örnekleri:
┌─────────────────────┬────────────────────────────────┐
│ Cron                │ Açıklama                        │
├─────────────────────┼────────────────────────────────┤
│ '0 0 * * *'         │ Her gün gece yarısı            │
│ '0 2 * * 1'         │ Her Pazartesi saat 02:00       │
│ '0 */4 * * *'       │ Her 4 saatte bir               │
│ '30 8 * * 1-5'      │ Hafta içi 08:30                │
│ '0 0 1,15 * *'      │ Ayın 1'i ve 15'i              │
└─────────────────────┴────────────────────────────────┘
# Örnek: Her gece güvenlik taraması
name: Nightly Security Scan

on:
  schedule:
    - cron: '0 3 * * *'  # Her gün 03:00 UTC

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run security audit
        run: npm audit --audit-level=high
      - name: Check for vulnerabilities
        run: npx snyk test

⚠️ Dikkat: Schedule trigger'lar sadece varsayılan branch'te (genellikle main) çalışır. Başka bir branch'teki schedule workflow'u çalışmaz. Ayrıca GitHub, yoğun dönemlerde schedule'ları 15-60 dakika geciktirebilir — dakika hassasiyetinde bir zamanlama bekleme.

workflow_dispatch — Manuel Tetikleme

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

      debug:
        description: 'Debug modu'
        required: false
        type: boolean
        default: false

      version:
        description: 'Versiyon (ör: 1.2.3)'
        required: false
        type: string

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: |
          echo "Deploying to: ${{ inputs.environment }}"
          echo "Version: ${{ inputs.version }}"
          echo "Debug: ${{ inputs.debug }}"
GitHub UI'da "Run workflow" butonu:
┌─────────────────────────────────────────────┐
│ Run workflow                                 │
│                                             │
│ Branch: main ▼                              │
│                                             │
│ Deploy ortamı: [staging ▼]                  │
│ Debug modu: [ ] ☐                           │
│ Versiyon: [1.2.3          ]                 │
│                                             │
│ [Run workflow]                              │
└─────────────────────────────────────────────┘

workflow_run — Başka Workflow Tamamlandığında

# CI tamamlandıktan sonra deploy et:
name: Deploy

on:
  workflow_run:
    workflows: ["CI Pipeline"]   # CI workflow'u adı
    types: [completed]
    branches: [main]

jobs:
  deploy:
    # Sadece CI başarılıysa deploy et
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run deploy

Ortam Değişkenleri (Environment Variables)

Üç Seviye Ortam Değişkeni

# 1. Workflow seviyesi — tüm job'larda geçerli:
env:
  NODE_ENV: production
  APP_NAME: my-app

jobs:
  build:
    runs-on: ubuntu-latest
    # 2. Job seviyesi — sadece bu job'da geçerli:
    env:
      DATABASE_URL: localhost:5432

    steps:
      - name: Test
        # 3. Step seviyesi — sadece bu step'te geçerli:
        env:
          TEST_MODE: true
        run: |
          echo $NODE_ENV       # production (workflow seviyesi)
          echo $DATABASE_URL   # localhost:5432 (job seviyesi)
          echo $TEST_MODE      # true (step seviyesi)
Ortam Değişkeni Öncelik Sırası (dar kapsam kazanır):

  Step env > Job env > Workflow env

  Aynı isimde değişken varsa, en dar kapsamdaki geçerli olur.

Varsayılan Ortam Değişkenleri

GitHub, her workflow'da otomatik olarak bazı değişkenleri tanımlar:

steps:
  - name: Show default env vars
    run: |
      echo "CI: $CI"                      # true (her zaman)
      echo "HOME: $HOME"                  # /home/runner
      echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY"  # user/repo
      echo "GITHUB_REF: $GITHUB_REF"      # refs/heads/main
      echo "GITHUB_SHA: $GITHUB_SHA"       # abc123...
      echo "GITHUB_ACTOR: $GITHUB_ACTOR"   # push yapan kişi
      echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE"  # checkout dizini
      echo "RUNNER_OS: $RUNNER_OS"         # Linux

Step Çıktıları (Outputs)

Bir step'in çıktısını başka bir step'te kullanmak:

steps:
  - name: Get version
    id: version                          # Bu step'e ID ver
    run: |
      VERSION=$(node -p "require('./package.json').version")
      echo "app_version=$VERSION" >> $GITHUB_OUTPUT

  - name: Use version
    run: echo "Version is ${{ steps.version.outputs.app_version }}"

Job Çıktıları

Bir job'un çıktısını başka bir job'da kullanmak:

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.ver.outputs.version }}
    steps:
      - id: ver
        run: echo "version=1.2.3" >> $GITHUB_OUTPUT

  deploy:
    needs: prepare
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying v${{ needs.prepare.outputs.version }}"

Secrets — Hassas Bilgileri Güvenle Yönet

API key'leri, token'ları, şifreleri workflow dosyasına yazmak felaket olur. Secret'lar bunun için var.

Secret Oluşturma

GitHub'da Secret Ekleme:

Repo bazlı:
  Repository → Settings → Secrets and variables → Actions
  → "New repository secret"
  → Name: DEPLOY_TOKEN
  → Secret: ghp_xxxxxxxxxxxx
  → Add secret

Organization bazlı:
  Organization → Settings → Secrets and variables → Actions
  → Tüm repo'larda veya seçili repo'larda kullanılabilir

Environment bazlı:
  Repository → Settings → Environments → "production"
  → Environment secrets → Add secret

Secret Kullanma

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

      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying with token..."
          # Secret değerleri loglarda otomatik MASKELENİR:
          # echo $API_KEY → ***
          curl -H "Authorization: Bearer $DEPLOY_TOKEN" \
            https://api.deploy.com/trigger

Secret Güvenlik Kuralları

🔒 SECRET GÜVENLİK:
────────────────────

1. Secret'lar loglarda otomatik maskelenir (*** gösterilir)
2. Fork'lardan gelen PR'larda secret'lara ERİŞİM YOK
3. Secret isimleri GITHUB_ ile başlayamaz (reserved)
4. Secret'ı echo ile yazdırmaya çalışma (maskelense bile)
5. Secret'ı URL'e veya dosyaya yazma — log'a sızabilir

# ❌ YAPMA:
- run: echo ${{ secrets.TOKEN }}          # Maskelenir ama riskli
- run: curl "https://api.com?key=$KEY"    # URL log'a yazılabilir

# ✅ YAP:
- run: curl -H "Authorization: Bearer $KEY" https://api.com
  env:
    KEY: ${{ secrets.API_KEY }}

GITHUB_TOKEN — Otomatik Token

GitHub, her workflow'da otomatik olarak GITHUB_TOKEN secret'ı sağlar. Ayrıca oluşturmana gerek yok:

steps:
  - name: Comment on PR
    uses: actions/github-script@v7
    with:
      github-token: ${{ secrets.GITHUB_TOKEN }}
      script: |
        github.rest.issues.createComment({
          issue_number: context.issue.number,
          owner: context.repo.owner,
          repo: context.repo.repo,
          body: '✅ CI passed!'
        })

  - name: Create release
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    run: gh release create v1.0.0 --generate-notes
GITHUB_TOKEN İzinleri:
┌─────────────────┬────────────────────┐
│ İzin            │ Varsayılan         │
├─────────────────┼────────────────────┤
│ contents        │ read (push: write) │
│ issues          │ write              │
│ pull-requests   │ write              │
│ packages        │ read               │
│ actions         │ read               │
│ metadata        │ read               │
└─────────────────┴────────────────────┘
# İzinleri daraltmak (en iyi pratik):
permissions:
  contents: read
  pull-requests: write

jobs:
  ...

💡 İpucu: Workflow'un başında permissions: ile minimum gerekli izinleri belirt. Bu, supply chain saldırılarına karşı koruma sağlar. GITHUB_TOKEN'ın gereksiz yere geniş izinleri olmasını önlersin.


Variables — Secret Olmayan Yapılandırma

Secret olmayan ama workflow'da kullanmak istediğin değerler için Variables kullan:

GitHub'da Variable Ekleme:
  Repository → Settings → Secrets and variables → Actions
  → Variables tab → "New repository variable"
  → Name: DEPLOY_ENVIRONMENT
  → Value: staging
steps:
  - name: Show variables
    run: |
      echo "Environment: ${{ vars.DEPLOY_ENVIRONMENT }}"
      echo "Region: ${{ vars.AWS_REGION }}"
Secret vs Variable:
┌────────────────┬────────────────────────┬───────────────────┐
│                │ Secret                 │ Variable          │
├────────────────┼────────────────────────┼───────────────────┤
│ Değer          │ Şifreli, maskelenir    │ Açık metin        │
│ Loglarda       │ *** (gizli)            │ Görünür           │
│ Kullanım       │ Token, API key, şifre  │ URL, isim, flag   │
│ Erişim         │ ${{ secrets.NAME }}    │ ${{ vars.NAME }}  │
└────────────────┴────────────────────────┴───────────────────┘

Matrix Strategy — Paralel Test Matrisi

Projenin farklı ortamlarda çalıştığını doğrulamak istiyorsun: farklı Node.js versiyonları, farklı işletim sistemleri, farklı veritabanları. Her kombinasyon için ayrı job yazmak yerine matrix strategy kullan.

Temel Matrix

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

Bu 3 paralel job oluşturur:

Job 1: node-version = 18
Job 2: node-version = 20
Job 3: node-version = 22

Çoklu Boyut Matrix

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    node-version: [18, 20, 22]
    # 2 × 3 = 6 paralel job!

runs-on: ${{ matrix.os }}
steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
Sonuç: 6 job paralel çalışır
┌─────────────────┬─────────┬─────────┬─────────┐
│                 │ Node 18 │ Node 20 │ Node 22 │
├─────────────────┼─────────┼─────────┼─────────┤
│ Ubuntu          │   ✅    │   ✅    │   ✅    │
│ Windows         │   ✅    │   ✅    │   ✅    │
└─────────────────┴─────────┴─────────┴─────────┘

include ve exclude

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    node-version: [18, 20]
    
    # Belirli kombinasyonları EKLE:
    include:
      - os: ubuntu-latest
        node-version: 22
        experimental: true        # Ekstra değişken

    # Belirli kombinasyonları ÇIKAR:
    exclude:
      - os: windows-latest
        node-version: 18          # Windows + Node 18 atla

fail-fast

strategy:
  fail-fast: true    # Varsayılan: true
  # Bir job başarısız olursa, diğerlerini de DURDUR
  
  fail-fast: false   # Bir job başarısız olsa bile diğerleri devam etsin
  matrix:
    node-version: [18, 20, 22]
fail-fast: true (varsayılan)
  Node 18: ✅ geçti
  Node 20: ❌ başarısız → diğerleri de İPTAL
  Node 22: ⏹️ iptal edildi

fail-fast: false
  Node 18: ✅ geçti
  Node 20: ❌ başarısız → diğerleri DEVAM
  Node 22: ✅ geçti
  → Sonuç: 1/3 başarısız

💡 İpucu: CI'da fail-fast: false kullanmak genelde daha iyi. Çünkü hangi ortamlarda hata olduğunu tam olarak görmek istersin. fail-fast: true zaman kazandırır ama bilgi kaybına yol açar.

max-parallel

strategy:
  max-parallel: 2    # Aynı anda en fazla 2 job çalışsın
  matrix:
    node-version: [18, 20, 22]
    # 3 job var ama aynı anda sadece 2 çalışır

Caching — Pipeline'ı Hızlandır

Her workflow çalışmasında temiz bir runner başlar. Yani npm install her seferinde tüm paketleri sıfırdan indirir. Bu çok yavaş. Cache ile önceki çalışmadaki paketleri yeniden kullanabilirsin.

Cache Olmadan vs Cache ile

Cache OLMADAN:
  npm ci → 120 saniye (her seferinde 200MB indirme)

Cache İLE:
  İlk çalışma: npm ci → 120 saniye (+ cache kaydet)
  Sonraki çalışmalar: npm ci → 15 saniye (cache'den yükle) ⚡

actions/setup-node ile Otomatik Cache

En kolay yol — setup-node action'ı cache'i otomatik yönetir:

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'           # npm cache'ini otomatik yönet
    # cache: 'yarn'        # Yarn kullanıyorsan
    # cache: 'pnpm'        # pnpm kullanıyorsan

Bu tek satır, ~/.npm dizinini package-lock.json hash'ine göre cache'ler.

Manuel Cache (actions/cache)

Daha fazla kontrol istersen:

- name: Cache node modules
  uses: actions/cache@v4
  id: npm-cache
  with:
    path: node_modules          # Cache'lenecek dizin
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

- name: Install dependencies
  if: steps.npm-cache.outputs.cache-hit != 'true'  # Cache yoksa yükle
  run: npm ci

Cache Anahtarı Nasıl Çalışır?

Cache Key: linux-node-abc123  (package-lock.json hash'i)

1. Çalışma başladı
2. Cache key "linux-node-abc123" ara
   ├── Bulundu (cache hit) → Cache'den yükle (hızlı!)
   └── Bulunamadı (cache miss)
       ├── restore-keys ile "linux-node-" prefixi dene
       │   ├── Bulundu → Eski cache yükle + yeni paketleri ekle
       │   └── Bulunamadı → Sıfırdan yükle
       └── Çalışma sonunda yeni cache kaydet

Farklı Diller İçin Cache

# Python:
- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'

# Ruby:
- uses: ruby/setup-ruby@v1
  with:
    ruby-version: '3.2'
    bundler-cache: true

# Java/Gradle:
- uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '21'
    cache: 'gradle'

# Go:
- uses: actions/setup-go@v5
  with:
    go-version: '1.22'
    cache: true

Cache Limitleri

GitHub Cache Limitleri:
─────────────────────────
- Repo başına toplam: 10 GB
- Tek cache girişi: max 10 GB
- 7 gün erişilmeyen cache otomatik silinir
- Branch bazlı: her branch kendi cache'ini kullanır
  (ama varsayılan branch'in cache'ine erişebilir)

Concurrency — Çakışan Workflow'ları Yönet

Aynı branch'e hızla birden fazla push yapılırsa, birden fazla workflow aynı anda çalışır. Bu gereksiz kaynak israfıdır.

# Aynı grupta çalışan eski workflow'u iptal et:
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

# Örnek: main branch'e 3 push yaptın
# Push 1 → workflow başladı
# Push 2 → Push 1'in workflow'u İPTAL, yeni workflow başladı
# Push 3 → Push 2'nin workflow'u İPTAL, yeni workflow başladı
# Sonuç: Sadece Push 3'ün workflow'u çalışır
# Deploy'da iptal etmek istemezsin — sıralı çalıştır:
concurrency:
  group: deploy-production
  cancel-in-progress: false    # Bekle, sırayla çalıştır

Gerçek Dünya Workflow'u: Tam Örnek

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

on:
  push:
    branches: [main, develop]
    paths-ignore:
      - '**.md'
      - 'docs/**'
      - '.github/ISSUE_TEMPLATE/**'
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 3 * * 1'    # Her Pazartesi 03:00 — haftalık güvenlik taraması

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

env:
  NODE_VERSION: '20'
  CI: true

permissions:
  contents: read
  pull-requests: write

jobs:
  # ──────────────────────────────────
  # Job 1: Lint ve Format Kontrolü
  # ──────────────────────────────────
  lint:
    name: 🔍 Lint & Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - run: npm ci

      - name: ESLint
        run: npm run lint

      - name: Prettier
        run: npx prettier --check .

  # ──────────────────────────────────
  # Job 2: Test Matrisi
  # ──────────────────────────────────
  test:
    name: 🧪 Test (Node ${{ matrix.node-version }})
    runs-on: ubuntu-latest
    needs: lint
    strategy:
      fail-fast: false
      matrix:
        node-version: [18, 20, 22]

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - run: npm ci
      - run: npm test -- --coverage

      - name: Upload coverage
        if: matrix.node-version == 20
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  # ──────────────────────────────────
  # Job 3: Build
  # ──────────────────────────────────
  build:
    name: 🏗️ Build
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4

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

      - run: npm ci
      - run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
          retention-days: 7

  # ──────────────────────────────────
  # Job 4: Güvenlik Taraması (Haftalık)
  # ──────────────────────────────────
  security:
    name: 🔒 Security Audit
    runs-on: ubuntu-latest
    if: github.event_name == 'schedule' || github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4

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

      - run: npm audit --audit-level=moderate
Bu workflow'un akışı:

push/PR:
  ┌──────┐     ┌────────────────────────┐     ┌───────┐
  │ Lint │────►│ Test (Node 18, 20, 22) │────►│ Build │
  └──────┘     └────────────────────────┘     └───────┘
   1 dk              3 dk (paralel)             1 dk

schedule (Pazartesi):
  ┌────────────┐
  │ Security   │
  │ Audit      │
  └────────────┘

Reusable Workflows — Tekrar Kullanılabilir Workflow

Birden fazla repo'da aynı CI pipeline'ı kullanıyorsan, kopyala-yapıştır yapmak yerine reusable workflow oluştur:

# .github/workflows/reusable-ci.yml (paylaşılan repo'da)
name: Reusable CI

on:
  workflow_call:           # Bu workflow başka workflow'lardan çağrılabilir
    inputs:
      node-version:
        required: false
        type: string
        default: '20'
    secrets:
      NPM_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 }}
      - run: npm ci
      - run: npm test
# Başka bir repo'dan çağır:
name: CI

on: [push, pull_request]

jobs:
  ci:
    uses: my-org/shared-workflows/.github/workflows/reusable-ci.yml@main
    with:
      node-version: '20'
    secrets:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Yaygın Hatalar

1. Secret'ı Echo ile Yazdırmak

# ❌ Secret değerini loga yazdırma!
- run: echo "Token: ${{ secrets.API_KEY }}"
# GitHub maskeler ama yine de riskli

# ✅ Secret'ı ortam değişkeni olarak kullan:
- run: curl -H "Auth: $TOKEN" https://api.com
  env:
    TOKEN: ${{ secrets.API_KEY }}

2. Cache Key'inde Hash Kullanmamak

# ❌ Sabit cache key — bağımlılıklar değişince eski cache kullanılır
- uses: actions/cache@v4
  with:
    key: my-cache

# ✅ Lock file hash'i ile key oluştur:
- uses: actions/cache@v4
  with:
    key: npm-${{ hashFiles('package-lock.json') }}

3. Gereksiz Yere Her Dosya Değişikliğinde Çalıştırmak

# ❌ README değiştirince bile CI çalışır:
on: push

# ✅ Sadece ilgili dosyalar değiştiğinde:
on:
  push:
    paths-ignore:
      - '**.md'
      - 'docs/**'
      - 'LICENSE'

Özet

  • Tetikleyiciler (triggers) workflow'un ne zaman çalışacağını belirler — push, pull_request, schedule, workflow_dispatch en yaygın olanlarıdır; paths ve paths-ignore ile gereksiz çalıştırmaları önle

  • Ortam değişkenleri workflow, job ve step seviyesinde tanımlanır — dar kapsam geniş kapsamı override eder; step çıktıları $GITHUB_OUTPUT ile paylaşılır

  • Secrets hassas bilgileri (token, API key) güvenli saklar — loglarda maskelenir, fork PR'larında erişilemez; GITHUB_TOKEN otomatik olarak sağlanır

  • Matrix strategy farklı ortam kombinasyonlarında paralel test sağlar — fail-fast: false ile tüm sonuçları gör, include/exclude ile matrixi özelleştir

  • Caching pipeline süresini dramatik şekilde düşürür — setup-node ile otomatik cache kullan veya actions/cache ile manuel kontrol sağla

  • Concurrency ile çakışan workflow'ları yönet — cancel-in-progress: true eski çalışmaları iptal ederek kaynak tasarrufu sağlar


*Bir sonraki derste CI pipeline'ını derinleştireceğiz: Lint, test, build, artifact ve branch protection!*