GitHub Actions Workflow Dosyası Yazımı: .github/workflows Klasörü Rehberi

Bir projede CI/CD kurulumu yaparken en çok zaman harcanan yer, pipeline’ın kendisinden çok o pipeline’ı tetikleyen workflow dosyasını doğru yazmak oluyor. GitHub Actions’a geçiş yapan çoğu sysadmin “YAML syntax’ı biliyorum, ne kadar zor olabilir ki” diye başlıyor ama birkaç saat sonra neden job’ların çalışmadığını, neden secret’ların görünmediğini ya da neden her push’ta tüm pipeline’ın baştan çalıştığını anlamaya çalışıyor. Bu rehberde .github/workflows dizinini sıfırdan ele alacağız, gerçek dünya senaryolarıyla birlikte.

Temel Kavramlar ve Dosya Yapısı

GitHub Actions workflow dosyaları YAML formatında yazılır ve .github/workflows/ dizininde saklanır. Bir repo içinde birden fazla workflow dosyası olabilir, her biri bağımsız çalışır. Dosya adının bir önemi yok ama anlamlı isimlendirme hayat kurtarır: deploy-production.yml, run-tests.yml, build-docker.yml gibi.

Temel bir workflow dosyasının iskelet yapısı şöyle görünür:

name: Uygulama Deployment

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Kodu çek
        uses: actions/checkout@v4

      - name: Basit test
        run: echo "Pipeline çalışıyor"

Bu dosyayı .github/workflows/deploy.yml olarak kaydettiğinizde, main branch’e her push veya pull request açıldığında otomatik tetiklenecektir.

Workflow Bileşenleri

Bir workflow dosyasında dört ana bölüm var:

  • name: Workflow’un GitHub arayüzünde görünen adı
  • on: Tetikleyici olaylar (trigger)
  • jobs: Paralel veya sıralı çalışan iş grupları
  • steps: Her job içindeki adımlar

Trigger (Tetikleyici) Olaylarını Anlamak

En çok karşılaşılan sorunlardan biri trigger’ları yanlış yapılandırmak. Yanlış bir trigger, ya hiç çalışmayan ya da çok sık çalışan pipeline’lara yol açar.

Push ve Pull Request Trigger’ları

on:
  push:
    branches:
      - main
      - 'release/**'
    paths:
      - 'src/**'
      - 'package.json'
    paths-ignore:
      - '**.md'
      - 'docs/**'

  pull_request:
    branches:
      - main
    types:
      - opened
      - synchronize
      - reopened

Burada birkaç kritik nokta var. paths filtresini kullanarak sadece belirli dosyalar değiştiğinde pipeline’ı tetikleyebilirsiniz. Örneğin sadece dokümantasyon değiştiyse tüm deployment pipeline’ını çalıştırmak kaynak israfı. paths-ignore ise tam tersi çalışır, belirtilen dosyalar değiştiğinde tetiklemez.

pull_request için types belirtmek de önemli. Varsayılan olarak opened, synchronize ve reopened eventleri dinleniyor ama siz sadece PR merge edildiğinde bir şey çalıştırmak istiyorsanız closed event’ini kullanmanız gerekir ve bunu if koşuluyla birleştirmeniz gerekir.

Zamanlanmış ve Manuel Trigger’lar

on:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deploy edilecek ortam'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
      debug_mode:
        description: 'Debug modunu etkinleştir'
        required: false
        type: boolean
        default: false

workflow_dispatch production deployment’larında çok işe yarıyor. Manuel tetikleme sırasında parametre almanızı sağlıyor. environment input’unu sonra ${{ github.event.inputs.environment }} şeklinde kullanabilirsiniz.

schedule için standart cron syntax kullanılıyor ama dikkat: GitHub Actions cron’ları UTC bazlı çalışıyor. Türkiye saatiyle gece 2’de çalıştırmak istiyorsanız UTC’ye göre hesaplamanız gerekiyor (yaz saatinde UTC+3, kış saatinde UTC+2).

Jobs ve Runner Yapılandırması

Runner Seçimi

jobs:
  test:
    runs-on: ubuntu-latest
    
  build-windows:
    runs-on: windows-latest
    
  deploy:
    runs-on: [self-hosted, linux, production]

GitHub’ın hosted runner’ları (ubuntu-latest, windows-latest, macos-latest) hızlı başlamak için idealdir ama her job için dakika bazlı ücretlendirilirsiniz. Self-hosted runner’lar kendi sunucunuzda çalışır, private network erişimi gerektiren deployment’lar için zorunludur. [self-hosted, linux, production] şeklinde etiketler kullanarak belirli runner’ları hedefleyebilirsiniz.

Job’lar Arası Bağımlılık

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Testleri çalıştır
        run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build al
        run: npm run build

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Staging'e deploy et
        run: echo "Staging deployment"

  deploy-production:
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Production'a deploy et
        run: echo "Production deployment"

needs ile job bağımlılıklarını tanımlıyorsunuz. deploy-production job’ı hem build hem de deploy-staging tamamlanmadan başlamıyor. Bu, production’a geçmeden önce staging’in başarıyla tamamlanmasını zorunlu kılıyor.

Environment Variables ve Secrets Yönetimi

Bu konu, yeni başlayanların en çok kafasının karıştığı alan. GitHub Actions’da birkaç farklı değişken türü var.

Değişken Hiyerarşisi

env:
  NODE_ENV: production
  APP_VERSION: '1.0.0'

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DATABASE_HOST: db.internal.company.com
    steps:
      - name: Deploy
        env:
          API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
          DATABASE_PASSWORD: ${{ secrets.DB_PASSWORD }}
        run: |
          echo "Uygulama versiyonu: $APP_VERSION"
          echo "Ortam: $NODE_ENV"
          ./deploy.sh --host $DATABASE_HOST

Değişken öncelik sırası şöyle çalışır: Step seviyesi > Job seviyesi > Workflow seviyesi. Aynı isimde değişken varsa en yakın scope kazanır.

Secret’lar GitHub repository ayarlarından veya organization ayarlarından tanımlanır. Workflow dosyasına asla plain text secret yazmayın. Secret değerleri log’larda otomatik olarak maskelenir ama buna rağmen secret’ları echo ile yazdırmaktan kaçının.

Context Kullanımı

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Context bilgilerini göster
        run: |
          echo "Branch: ${{ github.ref_name }}"
          echo "Commit SHA: ${{ github.sha }}"
          echo "Actor: ${{ github.actor }}"
          echo "Event: ${{ github.event_name }}"
          echo "Repository: ${{ github.repository }}"
          echo "Run ID: ${{ github.run_id }}"
          echo "Run Number: ${{ github.run_number }}"

Bu değerler özellikle Docker image tag’lama, deployment versiyonlama ve notification mesajlarında işe yarıyor. github.sha‘nın ilk 8 karakterini image tag olarak kullanmak yaygın bir pratik: ${{ github.sha }} yerine ${GITHUB_SHA::8} şeklinde bash substring kullanabilirsiniz.

Gerçek Dünya Senaryosu: Node.js Uygulaması CI/CD

Bir e-ticaret platformu düşünün. Her PR açıldığında testler çalışacak, main’e merge edildiğinde staging’e, tag push’landığında production’a deploy edilecek.

name: E-Ticaret Platform CI/CD

on:
  push:
    branches:
      - main
    tags:
      - 'v*'
  pull_request:
    branches:
      - main

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Node.js kurulumu
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Bağımlılıkları kur
        run: npm ci

      - name: Lint çalıştır
        run: npm run lint

      - name: Unit testleri çalıştır
        run: npm test -- --coverage

      - name: Coverage raporu yükle
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
          retention-days: 7

  build-and-push:
    needs: lint-and-test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
      image-digest: ${{ steps.build.outputs.digest }}

    steps:
      - uses: actions/checkout@v4

      - name: Container Registry'e giriş yap
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Image metadata hazırla
        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=sha,prefix=sha-

      - name: Docker image build et ve push et
        id: build
        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

Bu workflow’da dikkat etmeniz gereken birkaç detay var. outputs ile job’dan dışarıya değer aktarabilirsiniz, sonraki job’lar bu değerleri kullanabilir. permissions bloğu ile job’un ihtiyaç duyduğu minimum izinleri tanımlamak güvenlik açısından önemli. cache-from: type=gha Docker layer cache’ini GitHub Actions cache mekanizmasıyla entegre ediyor, build sürelerini ciddi ölçüde kısaltıyor.

Matrix Strategy ile Çoklu Ortam Testleri

jobs:
  test-matrix:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os:
          - ubuntu-latest
          - windows-latest
        node-version:
          - 18
          - 20
          - 22
        exclude:
          - os: windows-latest
            node-version: 18
      fail-fast: false

    steps:
      - uses: actions/checkout@v4

      - name: Node.js ${{ matrix.node-version }} kurulumu
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Testleri çalıştır
        run: npm test

Matrix strategy, her kombinasyon için ayrı bir job oluşturur. Yukarıdaki örnekte ubuntu-latest + Node 18, ubuntu-latest + Node 20, ubuntu-latest + Node 22, windows-latest + Node 20, windows-latest + Node 22 kombinasyonları çalışacak. exclude ile windows-latest + Node 18 kombinasyonu dışlandı. fail-fast: false ise bir kombinasyon başarısız olsa bile diğerlerinin çalışmaya devam etmesini sağlıyor.

Reusable Workflows ve Composite Actions

Büyük organizasyonlarda aynı workflow adımlarını defalarca yazmak yerine paylaşılabilir yapılar oluşturmak şart.

Reusable Workflow

.github/workflows/deploy-shared.yml dosyası:

name: Shared Deploy Workflow

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      KUBE_CONFIG:
        required: true
      SLACK_WEBHOOK:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - name: Kubernetes config ayarla
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config

      - name: Deployment güncelle
        run: |
          kubectl set image deployment/app 
            app=${{ inputs.image-tag }} 
            -n ${{ inputs.environment }}
          kubectl rollout status deployment/app 
            -n ${{ inputs.environment }} 
            --timeout=5m

      - name: Slack bildirimi gönder
        if: always()
        run: |
          STATUS="${{ job.status }}"
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} 
            -H 'Content-type: application/json' 
            -d "{"text":"Deploy to ${{ inputs.environment }}: ${STATUS}"}"

Bu reusable workflow’u başka bir workflow’dan şöyle çağırırsınız:

jobs:
  deploy-to-staging:
    uses: ./.github/workflows/deploy-shared.yml
    with:
      environment: staging
      image-tag: ghcr.io/myorg/myapp:sha-abc123
    secrets:
      KUBE_CONFIG: ${{ secrets.STAGING_KUBE_CONFIG }}
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

Conditional Steps ve Environment Protection

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.company.com

    steps:
      - name: Sadece main branch'te çalış
        if: github.ref == 'refs/heads/main'
        run: echo "Main branch"

      - name: Tag push'ta çalış
        if: startsWith(github.ref, 'refs/tags/v')
        run: echo "Version tag tespit edildi"

      - name: Başarısız olursa rollback yap
        if: failure()
        run: |
          kubectl rollout undo deployment/app -n production
          echo "Rollback tamamlandı"

      - name: Her durumda temizlik yap
        if: always()
        run: rm -f /tmp/deploy-config.yml

environment bloğu GitHub’daki environment korumalarını devreye sokar. Production environment’ını GitHub ayarlarından Required reviewers ile koruyabilirsiniz; workflow o noktaya geldiğinde onay bekler. environment.url ise deployment’ı GitHub arayüzünde tıklanabilir bir link olarak gösterir.

if koşulları için kullanabileceğiniz kontekst fonksiyonları:

  • startsWith(github.ref, ‘refs/tags/’): Ref’in belirli bir değerle başlayıp başlamadığını kontrol eder
  • contains(github.event.head_commit.message, ‘[skip ci]’): Commit mesajında belirli bir metin var mı diye bakar
  • failure(): Önceki adımlardan herhangi biri başarısız olduysa true döner
  • success(): Tüm önceki adımlar başarılıysa true döner
  • always(): Her koşulda çalışır, workflow iptal edilse bile
  • cancelled(): Workflow manuel iptal edildiğinde true döner

Artifact Yönetimi ve Cache Stratejileri

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

      - name: Dependency cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.npm
            node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Build
        run: npm run build

      - name: Build artifact yükle
        uses: actions/upload-artifact@v4
        with:
          name: production-build-${{ github.run_number }}
          path: dist/
          retention-days: 30
          if-no-files-found: error

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Build artifact indir
        uses: actions/download-artifact@v4
        with:
          name: production-build-${{ github.run_number }}
          path: dist/

      - name: Sunucuya yükle
        run: |
          rsync -avz --delete dist/ 
            ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/var/www/app/

Cache stratejisi workflow sürelerini büyük ölçüde etkiliyor. hashFiles('**/package-lock.json') ile lock dosyası değiştiğinde cache otomatik olarak geçersiz hale geliyor. restore-keys ise tam eşleşme bulunamadığında daha eski cache’i kullanmayı deniyor. Artifact’lar ise job’lar arası dosya paylaşımı için kullanılıyor ve ayrıca workflow run geçmişinde indirilebilir kalıyor.

Sorun Giderme İpuçları

Workflow dosyalarında sıkça karşılaşılan sorunlar ve çözümleri:

  • YAML indentation hatası: YAML’da tab değil space kullanın. 2 space standart. VS Code için YAML extension kurun, syntax highlighting hayat kurtarır.
  • Secret değeri görünmüyor: Secret’lar fork’lanmış repolardan gelen PR’larda default olarak erişilemez. Bu bir güvenlik önlemi.
  • Workflow tetiklenmiyor: on bloğunu kontrol edin. push: branches: main yazmak yerine push: branches: [main] veya push: branches:n - main formatını kullanın.
  • Self-hosted runner offline görünüyor: Runner servisinin çalıştığını kontrol edin: sudo systemctl status actions.runner.*
  • Job çok uzun sürüyor: Default timeout 6 saat. timeout-minutes: 30 ile job seviyesinde veya step seviyesinde kısıtlayabilirsiniz.
  • Concurrent run sorunu: Aynı anda birden fazla deployment’ın çakışmasını önlemek için concurrency kullanın:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Bu ayar, aynı branch’e arka arkaya push geldiğinde önceki çalışan workflow’u iptal eder ve sadece sonuncuyu çalıştırır. Özellikle hızlı geliştirme döngülerinde runner kaynaklarını verimli kullanmayı sağlar.

Sonuç

.github/workflows dizinini doğru yapılandırmak, CI/CD pipeline’ınızın güvenilirliğini ve sürdürülebilirliğini doğrudan etkiliyor. Trigger’ları gereksiz yere geniş tutmak kaynak israfına, çok dar tutmak ise bazı değişikliklerin kontrol edilmeden geçmesine neden oluyor.

Pratik bir yaklaşım olarak küçük başlayın: Önce basit bir lint ve test workflow’u oluşturun, çalıştığını gördükten sonra Docker build, artifact yönetimi ve deployment adımlarını ekleyin. Reusable workflow’ları erken aşamada tasarlamak, organizasyon genelinde tutarlılık sağlıyor ve yeni projeler için onboarding süresini ciddi ölçüde kısaltıyor.

Secret yönetimi ve environment korumalarını başından itibaren doğru kurmak, sonradan güvenlik açığı kapatmak için saatlerce uğraşmaktan çok daha kolay. GitHub Environments üzerindeki Required reviewers ve Wait timer özellikleri, production deployment’larına insan gözü katmanı eklemek için en pratik yol.

Son olarak, workflow dosyalarınızı da kod gibi değerlendirin: Pull request üzerinden review’a açın, değişiklik gerekçelerini commit mesajlarına yazın ve önemli değişiklikleri changelog’unuzda belgeleyin. Pipeline’ınız ne kadar şeffaf olursa, ekibiniz o kadar güvenle kullanır.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir