GitHub Actions ile Environment Protection Rules Kullanımı
Bir production ortamına kod deploy etmek her zaman heyecan verici ama bir o kadar da gergin bir süreçtir. Geceleri uyku kaçıran o “acaba bir şey gitti mi?” sorusunu en aza indirmenin yolu, deployment pipeline’ınıza akıllı güvenlik katmanları eklemekten geçiyor. GitHub Actions’ın Environment Protection Rules özelliği tam olarak bu noktada devreye giriyor.
Environment Protection Rules Nedir?
GitHub Actions’ta environment kavramı, deployment hedeflerinizi temsil eder. Development, staging, production gibi ortamlar birer environment olarak tanımlanır. Protection rules ise bu ortamlara yapılacak deploymentları belirli koşullara bağlamanızı sağlar.
Temel olarak şunları yapabilirsiniz:
- Required reviewers: Deployment başlamadan önce belirli kişilerin onay vermesini zorunlu kılmak
- Wait timer: Deployment öncesinde bekleme süresi tanımlamak
- Deployment branches: Sadece belirli branch’lerden deployment izni vermek
- Custom deployment protection rules: Üçüncü parti servislerle entegrasyon
Bu özellikler sayesinde “yanlış branch’ten production’a deploy” ya da “sabah 3’te kimse fark etmeden kritik değişiklik gitmesi” gibi senaryoların önüne geçebilirsiniz.
Environment Tanımlama
Öncelikle repository ayarlarından environment oluşturmanız gerekiyor. GitHub repo sayfasında Settings > Environments yolunu izleyin. Ama her şeyi workflow dosyasından da yönetebilirsiniz.
Basit bir deployment workflow’u şöyle görünür:
name: Deploy Application
on:
push:
branches:
- main
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to staging
run: |
echo "Staging ortamına deploy ediliyor..."
./scripts/deploy.sh staging
deploy-production:
runs-on: ubuntu-latest
environment: production
needs: deploy-staging
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Production ortamına deploy ediliyor..."
./scripts/deploy.sh production
Burada environment: production satırı, bu job’ın production environment’ına ait olduğunu belirtir. Eğer o environment için protection rules tanımladıysanız, job otomatik olarak bu kurallara tabi olur.
Required Reviewers Kurulumu
En sık kullanılan protection rule, deployment öncesi manuel onay mekanizmasıdır. GitHub UI’dan Settings > Environments > production > Required reviewers kısmına gidin ve onay vermesi gereken kişileri veya ekipleri ekleyin.
Onay mekanizması devreye girdiğinde workflow şöyle görünür:
name: Production Deployment Pipeline
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm install
npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build artifact
run: |
npm run build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy-production:
needs: build
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy
env:
DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
run: |
echo "Onay alındı, deploy başlıyor..."
rsync -avz dist/ user@production-server:/var/www/myapp/
Bu workflow çalıştığında, deploy-production job’ı başlamadan önce GitHub otomatik olarak onay bekleyecektir. Onaylayıcılara e-posta bildirimi gider, Actions sekmesinde “Waiting for approval” mesajı görünür.
Wait Timer ile Otomatik Bekleme
Bazen ekip içi iletişim için sadece birkaç dakika bile yeterli olabilir. Wait timer özelliği, deployment başlamadan önce belirlediğiniz dakika kadar bekler. Bu sürede bir sorun fark ederseniz workflow’u durdurabilirsiniz.
GitHub UI’dan environment ayarlarına girip Wait timer değerini dakika cinsinden girin. Örneğin 5 dakika, staging’den production’a geçiş için yeterli bir tampon süredir.
Bu süre, özellikle şu senaryolarda hayat kurtarır:
- Staging’de testler geçti ama monitoring dashboard’unda beklenmedik bir metrik artışı gördünüz
- Deploy pipeline’ı başlattıktan sonra bir meslektaşınız “dur, bu feature’da sorun var” dedi
- Canary deployment yapıyorsunuz ve ilk grup için bir süre beklemek istiyorsunuz
Branch Filtreleme ile Güvenlik
Protection rules’un en kritik parçalarından biri, hangi branch’lerden deployment yapılabileceğini kısıtlamaktır. Production environment için sadece main branch’ine izin vermek temel bir güvenlik önlemidir.
GitHub UI’da Deployment branches seçeneğini Selected branches olarak ayarlayın ve main pattern’i ekleyin. Ama bunu workflow dosyasında da destekleyen bir yapı kurabilirsiniz:
name: Environment-Aware Deployment
on:
push:
branches:
- main
- 'release/**'
- develop
jobs:
determine-environment:
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.set-env.outputs.environment }}
steps:
- name: Set target environment
id: set-env
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "environment=production" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == refs/heads/release/* ]]; then
echo "environment=staging" >> $GITHUB_OUTPUT
else
echo "environment=development" >> $GITHUB_OUTPUT
fi
deploy:
needs: determine-environment
runs-on: ubuntu-latest
environment: ${{ needs.determine-environment.outputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Deploy to ${{ needs.determine-environment.outputs.environment }}
run: |
TARGET_ENV="${{ needs.determine-environment.outputs.environment }}"
echo "Deploy ediliyor: $TARGET_ENV"
./scripts/deploy.sh "$TARGET_ENV"
Bu yaklaşım, branch adına göre otomatik olarak doğru environment’ı seçer ve her environment’ın kendi protection rules’larına tabi olmasını sağlar.
Environment Secrets ile Güvenli Konfigürasyon
Environment protection rules ile birlikte kullanmanın en güçlü yanlarından biri, her environment için ayrı secrets tanımlayabilmektir. Production veritabanı şifresi, staging ortamında bulunmamalıdır.
name: Secure Multi-Environment Deploy
on:
push:
branches: [main, develop]
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- name: Deploy to ECS
env:
DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }}
APP_SECRET_KEY: ${{ secrets.APP_SECRET_KEY }}
REDIS_URL: ${{ secrets.REDIS_URL }}
run: |
echo "Container image build ediliyor..."
docker build -t myapp:${{ github.sha }} .
echo "ECR'a push ediliyor..."
aws ecr get-login-password | docker login --username AWS
--password-stdin ${{ secrets.ECR_REGISTRY }}
docker tag myapp:${{ github.sha }}
${{ secrets.ECR_REGISTRY }}/myapp:${{ github.sha }}
docker push ${{ secrets.ECR_REGISTRY }}/myapp:${{ github.sha }}
echo "ECS service güncelleniyor..."
aws ecs update-service
--cluster ${{ secrets.ECS_CLUSTER_NAME }}
--service myapp-service
--force-new-deployment
Burada secrets.DB_CONNECTION_STRING production environment’ında production veritabanına, staging environment’ında ise staging veritabanına işaret eder. Aynı workflow kodu, farklı ortamlarda farklı değerlerle çalışır.
Gerçek Dünya Senaryosu: E-ticaret Deployment Pipeline
Bir e-ticaret platformu yönettiğinizi düşünün. Black Friday yaklaşıyor ve deployment sürecinde sıfır hata toleransınız var. Şöyle bir pipeline kurabilirsiniz:
name: E-Commerce Production Pipeline
on:
push:
branches: [main]
workflow_dispatch:
inputs:
skip_staging:
description: 'Staging adımını atla (acil hotfix)'
required: false
type: boolean
default: false
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Run unit tests
run: php artisan test --testsuite=Unit
integration-tests:
needs: unit-tests
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: testpass
MYSQL_DATABASE: testdb
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- name: Run integration tests
env:
DB_HOST: 127.0.0.1
DB_PASSWORD: testpass
run: php artisan test --testsuite=Integration
deploy-staging:
needs: integration-tests
if: ${{ !inputs.skip_staging }}
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.mystore.com
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: |
echo "Staging deploy başlıyor: ${{ github.sha }}"
ssh -i ${{ secrets.DEPLOY_SSH_KEY }}
[email protected]
"cd /var/www/staging && git pull && composer install
&& php artisan migrate --force && php artisan cache:clear"
smoke-tests:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run smoke tests against staging
run: |
sleep 30 # Deploy'un tamamlanması için bekle
curl -f https://staging.mystore.com/health || exit 1
curl -f https://staging.mystore.com/api/products |
python3 -c "import sys,json; d=json.load(sys.stdin);
sys.exit(0 if len(d) > 0 else 1)"
echo "Smoke testler başarılı!"
deploy-production:
needs: [smoke-tests]
runs-on: ubuntu-latest
environment:
name: production
url: https://mystore.com
steps:
- uses: actions/checkout@v4
- name: Pre-deployment notification
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }}
-H 'Content-type: application/json'
--data "{"text":"Production deploy başlıyor: ${{ github.sha }}"}"
- name: Enable maintenance mode
run: |
ssh [email protected]
"cd /var/www/production && php artisan down --secret=bypass123"
- name: Deploy application
run: |
ssh [email protected]
"cd /var/www/production && git pull origin main
&& composer install --no-dev --optimize-autoloader
&& php artisan migrate --force
&& php artisan config:cache
&& php artisan route:cache
&& php artisan view:cache"
- name: Disable maintenance mode
run: |
ssh [email protected]
"cd /var/www/production && php artisan up"
- name: Post-deployment verification
run: |
sleep 15
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://mystore.com)
if [ "$HTTP_STATUS" != "200" ]; then
echo "HATA: Site yanıt vermiyor! HTTP $HTTP_STATUS"
exit 1
fi
echo "Deployment başarılı! HTTP $HTTP_STATUS"
Bu pipeline’da dikkat edin: staging atlanabilir ama sadece workflow_dispatch ile manuel tetiklenirse ve skip_staging: true seçildiyse. Normal push akışında her zaman staging üzerinden geçilir.
Deployment Durumunu Takip Etmek
Protection rules ile birlikte deployment geçmişini de takip etmek istersiniz. GitHub’ın deployment API’sini kullanarak bunu otomatize edebilirsiniz:
name: Deployment with Status Tracking
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- name: Create deployment record
id: deployment
uses: actions/github-script@v7
with:
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'production',
description: 'Automated production deployment',
auto_merge: false,
required_contexts: []
});
return deployment.data.id;
- name: Deploy application
id: deploy
run: |
echo "Deploy ediliyor..."
./scripts/deploy.sh
echo "deploy_url=https://myapp.com" >> $GITHUB_OUTPUT
- name: Update deployment status - success
if: success()
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deployment.outputs.result }},
state: 'success',
environment_url: 'https://myapp.com',
description: 'Deployment başarıyla tamamlandı'
});
- name: Update deployment status - failure
if: failure()
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deployment.outputs.result }},
state: 'failure',
description: 'Deployment başarısız oldu'
});
Sık Karşılaşılan Sorunlar ve Çözümleri
Environment bulunamıyor hatası: Workflow’da belirttiğiniz environment adı GitHub’da tanımlı değilse job hata verir. Environment adlarının büyük-küçük harf duyarlı olduğunu unutmayın. Production ile production farklı şeylerdir.
Onay bildirimleri gitmiyor: Required reviewers olarak eklediğiniz kullanıcıların e-posta bildirimlerini açmış olması ve repository’e erişimlerinin bulunması gerekir.
Branch kısıtlaması çalışmıyor: GitHub’da environment’a özel deployment branch kısıtlamasını sadece public repolarda ve GitHub Pro/Team/Enterprise hesaplarında kullanabilirsiniz. Free plan private repolarda bu özellik kısıtlıdır.
Wait timer atlanıyor: Sadece organization owner veya repository admin rolündeki kullanıcılar wait timer’ı atlayabilir. Bu bir güvenlik açığı değil, kasıtlı bir tasarım kararıdır.
Concurrency ile Deployment Çakışmalarını Önlemek
Birden fazla push aynı anda deploy tetiklediğinde ne olur? Concurrency ayarı bu sorunu çözer:
name: Safe Production Deployment
on:
push:
branches: [main]
concurrency:
group: production-deployment
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy
run: ./scripts/deploy.sh
cancel-in-progress: false ayarı ile mevcut deployment tamamlanmadan yeni deploy başlamaz. Böylece yarım kalan bir deployment yüzünden ortamınız tutarsız duruma girmez. cancel-in-progress: true yaparsanız eski deployment iptal edilip yenisi başlar, ancak production için bu genellikle tehlikeli bir seçenektir.
Sonuç
GitHub Actions Environment Protection Rules, sıradan bir CI/CD pipeline’ını gerçek anlamda güvenli bir deployment sürecine dönüştürür. Bu özellikleri hayata geçirdikten sonra ekibinizde şunları fark edeceksiniz:
- Production deploymentları artık belgelenmiş ve izlenebilir bir süreç haline gelir
- “Kim deploy etti, ne zaman?” sorularının cevabı her zaman GitHub Actions geçmişinde mevcuttur
- Yanlış branch’ten yanlış ortama deployment vakaları neredeyse sıfıra iner
- Ekip içi deployment koordinasyonu e-posta veya Slack yerine doğrudan GitHub üzerinden yönetilir
Başlangıç için minimum şunları yapmanızı öneririm: production environment’ınıza en az bir required reviewer ekleyin, deployment branch’ini main ile kısıtlayın ve environment-specific secrets kullanmaya başlayın. Bu üç adım bile deployment güvenliğinizi önemli ölçüde artıracaktır.
Daha karmaşık senaryolar için GitHub’ın custom deployment protection rules API’sine bakabilirsiniz. Bu sayede kendi güvenlik sistemlerinizi, third-party monitoring araçlarını ve change management süreçlerinizi doğrudan deployment pipeline’ınıza entegre edebilirsiniz.
