İçeriğe atla
Ayhan Sipahi Ayhan Sipahi

GitHub Environments: Deployment İçin Manuel Onay Kapıları

Üretim deploy'ları gerçek bir onay adımı ister: GitHub Environment, native koruma kuralları ve environment'a bağlı secret'lar; if: hilesi ya da marketplace değil.

Üretim deploy’larında insan onayı gerekir, ama çoğu ekip yanlış parçayı seçer. En sık görülen üç şekil şudur: bir label’a bakan workflow if: koşulu, issues: write izniyle yorum bekleyen marketplace onay action’ı, ya da herhangi bir katkıcının tetikleyebildiği workflow_dispatch butonu. Artifact’ı main üzerinden bir kez üretin, staging’e otomatik düşsün, aynı artifact production’a ancak bir PM ya da QA bir GitHub Environment kapısında onay verdikten sonra promote edilsin. Aynı artifact, iki environment, tek onay. Bu kapı hem deploy credential’ını hem de insan onayını birlikte kapsar ve run’a bağlı bir audit kaydı bırakır.

Environment nesnesi

GitHub Environment, repo üzerinde adlandırılmış bir nesnedir ve üç şey tutar: environment secret’ları, environment variable’ları ve deployment protection rule’lar. UI’da Settings → Environments altında, REST API’da /repos/{owner}/{repo}/environments adresinde yaşar. Bir job environment: ile bir environment’ı hedef aldığında, koruma kuralları geçmeden ne o environment’ın secret’larını çözebilir ne de kendi adımlarını çalıştırabilir. Koruma kuralı ile secret erişimi aynı kapıdır; bu özellik soyutlamayı değerli kılan şeydir.

Üç deployment protection rule

Bunlar branch protection rule değil. Branch protection (ve yeni repository ruleset’ler) main’e merge’ü kapatır. Deployment protection rule ise Environment nesnesinde yaşar ve o environment’ı hedefleyen deploy’u kapatır; merge’ten sonra, bir job artifact’ı alıp deploy etmeye çalıştığında devreye girer. GitHub üç tane sunuyor.

Required reviewers. En fazla altı kullanıcı veya takım. Onaycıların repo’ya en azından read erişimi olmalıdır. Listedeki onaycılardan yalnızca birinin onay vermesi yeterlidir; diğerleri görmezden gelebilir. Onay formundaki yorum kutusu run başına audit izini taşır. Prevent self-review anahtarı vardır ve default’u kapalıdır; her üretim environment’ında bunu açın.

Wait timer. 1 ile 43.200 dakika (30 gün) arasında bir değer. Deploy job’ı tetiklendikten sonra bu süre boyunca Waiting durumunda bekler. Bekleme süresi faturalandırılan Actions dakikalarına dahil edilmez.

Deployment branch and tag policies. Üç mod: kısıtlama yok, sadece protected branch’ler, ya da isim deseniyle seçilen branch ve tag’ler (main, release/*, v*.*.*). Hangi ref’lerin bu environment’a deploy talebi açabileceğini filtreler; bu olmadan bir feature branch kapıda bekleyip onay isteyebilir.

Bir de kaçış kapısı var. Bir job, job-level deployment: false (Mart 2026) ayarıyla bir environment’ı hedef alabilir; kapıda kuyruklanmaz, deployment kaydı oluşmaz. Bu, plan-only iş yükleri için doğru şekildir (terraform plan, dry-run build, manifest üretici gibi environment secret’ına read erişimi isteyen ama onaya düşmemesi gereken job’lar). Kısıtlama: deployment: false, custom deployment protection rule’lar ile birlikte kullanılamaz; özel kural taşıyan bir environment’ı hedef alan her job hâlâ auto-deploy gerektirir.

Adım adım kurulum

main üzerine atılan bir tag artifact’ı bir kez üretir, registry’ye push’lar ve staging’e otomatik deploy eder. Ayrı bir workflow_dispatch workflow’u aynı image’ı production’a promote eder ve Environment kapısında PM ya da QA onayını bekler. Build stage başına tekrar etmez; production’a giden byte’lar staging’i geçen byte’larla aynıdır.

# .github/workflows/release.yml
# main'e tag push'unda: artifact bir kez build edilir, staging'e otomatik deploy edilir.
name: release
on:
  push:
    tags: ['v*.*.*']

permissions:
  contents: read
  packages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
    steps:
      - uses: actions/checkout@v6
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - uses: actions/checkout@v6
      - run: ./deploy.sh staging "${{ needs.build.outputs.image }}"
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

staging yine bir Environment’tır; staging deploy credential’ını, staging URL’sini ve dilerseniz bir tag policy’sini tutar. Required reviewer yoktur, dolayısıyla deploy job’ı build biter bitmez çalışır.

Production ayrı bir workflow’ta yaşar. Input olarak bir tag alır, asla yeniden build etmez ve registry’deki mevcut image’ı çeker. production Environment’ı required-reviewers listesini taşır; bir PM ya da QA onaylayana kadar run kapıda bekler.

# .github/workflows/promote.yml
# Mevcut bir image'ı production'a manuel promote. Required reviewer kapıda bekletir.
name: promote
on:
  workflow_dispatch:
    inputs:
      tag:
        description: 'Promote edilecek release tag (orn. v1.4.2)'
        required: true

concurrency:
  group: deploy-production-${{ inputs.tag }}
  cancel-in-progress: false

permissions:
  contents: read
  packages: read
  id-token: write

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - uses: actions/checkout@v6
      - run: ./deploy.sh production "ghcr.io/${{ github.repository }}:${{ inputs.tag }}"
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

DEPLOY_TOKEN her Environment’ta ayrı bir environment secret’ı olarak yaşar; staging deploy staging token’ını, production deploy production token’ını okur; iki workflow birbirinin secret’ına dokunamaz. id-token: write izni, deploy adımının OIDC token üretip kısa ömürlü cloud credential alabilmesi içindir. production Environment’ına v*.*.* desenli bir tag policy koymak, promote workflow’unun yalnızca release tag’leriyle çalışmasını sağlar; workflow_dispatch’i bir feature branch adıyla tetikleyen biri kapıda durur. promote.yml’in başındaki concurrency bloğu da aynı tag için iki eşzamanlı promote denemesinin yarışmasını engeller; ikincisi birincisi bitene kadar kuyrukta bekler.

Onaycı tarafı şöyle işler: deploy yetkili biri gh workflow run promote.yml -f tag=v1.4.2 der ya da UI’da Run workflow der. GitHub deploy-production job’ını kuyruğa alır, Deployments görünümüne yazar ve listedeki onaycılara mail atar. Onaycı Review deployments der, production’ı işaretler, dilerse bir yorum yazar ve Approve and deploy ya da Reject der. Yorum, audit string’i olarak saklanır.

Aynı akışın API tarafı, change ticket bot’ları için faydalıdır:

# Mevcut bir tag'e karşı promote workflow'unu tetikle
gh workflow run promote.yml -f tag=v1.4.2

# Oluşan run id'sini bul
gh run list --workflow=promote.yml --limit 1

# O run için bekleyen onayları listele
gh api /repos/OWNER/REPO/actions/runs/RUN_ID/pending_deployments

# API üzerinden onayla
gh api -X POST /repos/OWNER/REPO/actions/runs/RUN_ID/pending_deployments \
  -F environment_ids[]=PROD_ENV_ID \
  -F state=approved \
  -F comment="CHG-12345 ile onaylandı"

state değeri approved ya da rejected olur. Reddedilen run, deploy-production job’ını durdurur ve production environment’ını o run için failed olarak işaretler; image registry tag’inde olduğu gibi durur. Rollback aynı akıştır, sadece tag farklı: en son sağlam v*.*.* ile promote.yml’i tetikleyin, kapıda onaylayın, deploy edin. Artifact değişmez olduğu için ayrı bir rollback workflow’una ihtiyaç yoktur.

Onaylandı

Reddedildi

main'den tag push

Build + image push

Staging'e deploy (auto)

PM / QA doğrulaması

Promote workflow tetiklenir

Production gate

Aynı image production'a

Deploy yok, audit yazıldı

Sık atlanan ayarlar

En çok kaçırılan üç ayar, workflow dosyalarından uzakta, environment ayar sayfasında durur.

Kendi kendine onay default’u

Default’ta deploy’u tetikleyen kullanıcı, required-reviewers listesinde de yer alıyorsa kendi deploy’unu onaylayabilir. Görev ayrımı (separation of duties) zorunluluğu olan ekipler için bu yanlış bir default’tur. Prevent self-review anahtarı Ekim 2023’ten beri environment ayar sayfasında durur ve tek tıklamayla açılır; workflow tarafına bir etkisi yoktur. Bunu her repoya uyguladığınız environment template’inin parçası yapın.

Onaycı takımlar mı, kişiler mi

Required-reviewers listesi kendi başına bir listedir. CODEOWNERS değildir ve CODEOWNERS kuralları buraya akmaz. Listeye takım ekleyin. Onaycı rotasyonu böylece takım üyeliğinde yaşar, onlarca repoya dağılmış environment ayarlarına dokunmanız gerekmez. Kişi eklerseniz, birisi her takım değiştirdiğinde o ayarları düzelten kişi siz olursunuz.

Asıl kapı: environment secret’ı

Environment’ın güvenlik gerekçesi insan duraklaması değil, deploy credential’ının kapı geçene kadar okunamaz olmasıdır. Repo secret’ı, repo’daki her workflow tarafından çözülebilir; PR tetiklemeli akışlar ya da label hilesiyle atlatılmış onay yolları da buna dahildir. Environment secret’ı, ancak environment: hedefinin koruma kurallarını geçtiği bir job tarafından çözülebilir. Üretim token’ınız repo secret’ında duruyorsa kapınız YAML’daki bir yorumdan ibarettir.

Environment’ın yanlış araç olduğu durumlar

Environment’ın doğru cevap olmadığı üç durum.

GitOps repo’ları. Rollout’u Argo CD ya da Flux yönetiyorsa, GHA workflow yalnızca bir PR açar ya da manifest push’lar. Onay kapısı, sync politikasının yaşadığı CD controller’a aittir. PR açan workflow’a Environment koymak insanı yanlış noktaya yerleştirir.

Repo’lar arası orchestration. Bir orchestrator workflow başka repo’larda child workflow’lar tetikliyorsa, kapı orchestrator’da olur. Hem orchestrator’ı hem de child’ları kapı arkasına koyarsanız tek deploy için iki onay sorulur ve onaycılar bir süre sonra okumadan onaylar.

Deploy hedefi olmayan job’lar. Housekeeping cron’ları, dependency-update bot’ları ve üretim hedefi olmayan diğer iş yükleri Environment istemez. Checklist tutturmak için eklemek, güvenlik kazancı olmadan sürtünme yaratır.

GHEC, GHES, Free planı

Plan kapsamı, ikincil yazıların çoğunun yanlış yaptığı kısımdır. GitHub ifadeleri zaman zaman değiştiriyor; bir plana göre tasarım yapmadan önce Plans sayfasını kendi hesabınızla karşılaştırın. Güncel dokümana göre:

  • Free. Environment’lar public repo’larda koruma kuralları dahil çalışır. Private repo’larda environment nesnesi vardır ama required reviewer ve wait timer sunulmaz.
  • Pro ve Team. Environment’lar private repo’larda çalışır. Required reviewer ve wait timer ise bu planlarda yalnızca public repo’larda kullanılabilir.
  • Enterprise. Required reviewer, wait timer, branch policy ve custom deployment protection rule’ların hepsi private ve internal repo’larda çalışır.

Üretim kodunuz Pro ya da Team planındaki bir private repo’da yaşıyorsa, elde ettiğiniz tek deployment protection rule branch ve tag policy’sidir. Bu, feature branch’in üretim deploy’u talep etmesini durdurur ama reviewer kapısı elinizde olmaz. İnsan onayı ve audit kaydı için Enterprise gerekir.

Custom deployment protection rule

“Şu an deploy edebilir miyiz?” sorusunun kaynağı GitHub dışında yaşıyorsa, bir GitHub App’i custom deployment protection rule olarak kaydedebilirsiniz. App deployment_protection_rule webhook’una abone olur ve REST API’ye geri dönüş yaparak onaylar ya da reddeder. ServiceNow change window’u, Datadog deploy gate, Honeycomb SLO kontrolü ya da kendi ticket sisteminizle entegrasyon noktası budur. Public-beta duyurusu Nisan 2023 tarihli ve docs sayfası artık beta etiketi taşımıyor. Bir compliance tasarımı bu özelliğe yaslanacaksa, önce o sayfadaki destek ifadesini okuyun.

Trade-off açıktır: dış sistem deploy’u durdurabilen bir bağımlılık olur ve GitHub App’in diğer kritik entegrasyonlar kadar ops dikkati ister. Mevcut bir change-management sistemi varsa ve gerçek kayıt kaynağı oysa kullanın. Sırf modern görünmek için kurmayın.

Kapanış

İki workflow yerine oturduktan sonra geri kalan ağırlığı üç ayar taşır: her deploy credential’ı kendi Environment’ının secret’ında, Prevent self-review açık ve production environment’ında yalnızca release tag’lerinin promote edebilmesi için v*.*.* desenli bir tag policy. Sınır durumları nettir: GitOps controller’lar kendi kapılarını kurar, orchestrator’lar child’larını yönetir, housekeeping job’ları hiçbirini istemez. Geri kalan her şey default’a oturur.

Kaynaklar

İlgili yazılar