GitHub Actions ile WordPress Deployment Otomasyonu

Zamanında bir WordPress sitesini güncellemek için sunucuya SSH ile bağlanıp dosyaları tek tek FTP’ye atmak zorunda kalmış biri olarak söyleyeyim: bu işi elle yapmak hem yorucu hem de hata yapmaya açık. Bir gün production’a yanlış dosyayı attım, sitenin yarısı bozuldu, saatlerce uğraştım. O günden sonra her şeyi otomasyona bırakmaya karar verdim. GitHub Actions bu noktada gerçek bir kurtarıcı oluyor.

Bu yazıda sıfırdan başlayıp production’da çalışan bir WordPress CI/CD pipeline’ı kurmanın tüm adımlarını anlatacağım. Sadece “şu YAML dosyasını kopyala yapıştır” değil, neden böyle yaptığımızı da açıklayacağım.

Neden GitHub Actions?

Jenkins kurarsın, bakım istersin. GitLab CI kullanırsın, ayrı bir platform öğrenirsin. Ama GitHub Actions zaten kodun olduğu yerde. Ayrı bir CI sunucusu kurmak yok, aylık ekstra ücret yok (public repolar için tamamen ücretsiz, private repolar için aylık 2000 dakika ücretsiz geliyor), ekibin alışmak için ciddi zaman harcaması yok.

WordPress deployment için GitHub Actions’ın bize sağladıkları:

  • Push tetiklemeli deployment: main branch’e push atınca otomatik deploy
  • Secrets yönetimi: SSH key, veritabanı şifresi gibi hassas bilgileri güvenle saklama
  • Çoklu ortam desteği: Staging ve production için farklı workflow’lar
  • Rollback imkanı: Bir şeyler ters gidince önceki versiyona dönme
  • Bildirim entegrasyonu: Slack, e-posta veya Telegram’a deployment bildirimi gönderme

Proje Yapısını Hazırlama

Önce bir WordPress projesinin klasik GitHub repository yapısına bakalım. Burada önemli bir nokta: wp-config.php ve uploads/ dizini asla Git’e girmemeli.

wordpress-site/
├── .github/
│   └── workflows/
│       ├── deploy-staging.yml
│       └── deploy-production.yml
├── wp-content/
│   ├── themes/
│   │   └── benim-temam/
│   ├── plugins/
│   │   └── ozel-plugin/
│   └── mu-plugins/
├── .gitignore
├── composer.json
└── README.md

.gitignore dosyası kritik. Yanlış yapılandırılırsa ya çok fazla dosya commit edilir ya da gerekli dosyalar eksik kalır:

# .gitignore icerigi
wp-config.php
wp-config-local.php
.env
.env.*

# WordPress core dosyalari (Composer ile yonetiliyorsa)
/wp-admin/
/wp-includes/
/wp-*.php
/index.php
/xmlrpc.php
/license.txt
/readme.html

# Uploads dizini
wp-content/uploads/
wp-content/upgrade/
wp-content/cache/
wp-content/backup-db/

# Node modulleri
node_modules/
vendor/

# OS dosyalari
.DS_Store
Thumbs.db

# IDE dosyalari
.idea/
.vscode/
*.swp

Sunucu Tarafını Hazırlama

GitHub Actions, sunucunuza bağlanabilmek için SSH erişimine ihtiyaç duyar. Bunun için özel bir deployment kullanıcısı oluşturmanızı şiddetle tavsiye ederim. Root ile deployment yapmak güvenlik açısından kabul edilemez.

# Sunucuda deployment kullanicisi olustur
sudo adduser deployer
sudo usermod -aG www-data deployer

# Deployment dizini olustur ve izinleri ayarla
sudo mkdir -p /var/www/wordpress-site
sudo chown deployer:www-data /var/www/wordpress-site
sudo chmod 2775 /var/www/wordpress-site

# SSH dizinini olustur
sudo -u deployer mkdir -p /home/deployer/.ssh
sudo -u deployer chmod 700 /home/deployer/.ssh

Şimdi GitHub Actions için SSH key çifti oluşturalım. Bunu local makinende yap, sunucuda değil:

# ED25519 key olustur (RSA'dan daha guvenli ve hizli)
ssh-keygen -t ed25519 -C "github-actions-deployer" -f ~/.ssh/github_actions_deployer

# Public key'i sunucuya ekle
ssh-copy-id -i ~/.ssh/github_actions_deployer.pub deployer@sunucu-ip-adresi

# Ya da manuel olarak
cat ~/.ssh/github_actions_deployer.pub | ssh deployer@sunucu-ip-adresi "cat >> ~/.ssh/authorized_keys"

# Private key'i ekrana yazdir - bu GitHub Secrets'a gidecek
cat ~/.ssh/github_actions_deployer

Private key’i kopyalayıp GitHub repository’sine gidiyoruz: Settings > Secrets and variables > Actions > New repository secret yolunu takip ediyoruz.

Eklememiz gereken secret’lar:

  • SSH_PRIVATE_KEY: Az önce kopyaladığımız private key
  • SSH_HOST: Sunucunun IP adresi veya domain’i
  • SSH_USERNAME: deployer
  • SSH_PORT: Genellikle 22, değiştirdiyseniz o port
  • WP_DB_NAME: Production veritabanı adı
  • WP_DB_USER: Veritabanı kullanıcısı
  • WP_DB_PASSWORD: Veritabanı şifresi
  • SLACK_WEBHOOK_URL: Bildirimler için (opsiyonel)

Temel Deployment Workflow’u

Şimdi asıl kısma geliyoruz. Staging için workflow dosyamızı yazalım:

# .github/workflows/deploy-staging.yml
name: Deploy to Staging

on:
  push:
    branches:
      - develop
  workflow_dispatch:

env:
  PHP_VERSION: '8.2'
  NODE_VERSION: '18'

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          extensions: mbstring, intl, gd, zip
          coverage: none

      - name: Install Composer dependencies
        run: composer install --prefer-dist --no-progress --no-dev

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

      - name: Install npm dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

  deploy:
    name: Deploy to Staging Server
    runs-on: ubuntu-latest
    needs: test
    environment: staging
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}

      - name: Install Composer dependencies
        run: composer install --prefer-dist --no-progress --no-dev --optimize-autoloader

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Install npm ve build
        run: |
          npm ci
          npm run build

      - name: Deploy via SSH
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            cd /var/www/staging-site
            git fetch origin develop
            git reset --hard origin/develop
            composer install --no-dev --optimize-autoloader
            wp cache flush --allow-root
            echo "Staging deployment tamamlandi: $(date)"

Production Deployment Workflow’u

Production için çok daha dikkatli olmalıyız. Burada backup alma, maintenance mode, rollback mekanizması gibi ekstra adımlar şart:

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

on:
  push:
    branches:
      - main
    tags:
      - 'v*.*.*'
  workflow_dispatch:
    inputs:
      reason:
        description: 'Deployment nedeni'
        required: true
        default: 'Manuel deployment'

jobs:
  security-check:
    name: Security Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Check for sensitive data
        run: |
          # Hassas veri kontrolu
          if grep -r "password" --include="*.php" --exclude-dir=vendor . | grep -v "password_hash|wp_check_password|#|//"; then
            echo "UYARI: Kod icinde sifre benzeri ifade bulundu!"
            exit 1
          fi
          echo "Guvenlik kontrolu gecti"

  build:
    name: Build Application
    runs-on: ubuntu-latest
    needs: security-check
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup PHP 8.2
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: mbstring, intl, gd, zip, opcache

      - name: Composer install
        run: composer install --no-dev --optimize-autoloader --prefer-dist

      - name: Node setup ve build
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          
      - run: npm ci && npm run build:production

      - name: Build artifacts'i sakla
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: |
            wp-content/
            vendor/
            composer.lock
          retention-days: 7

  deploy-production:
    name: Production Deploy
    runs-on: ubuntu-latest
    needs: build
    environment: production
    
    steps:
      - uses: actions/checkout@v4

      - name: Artifact'lari indir
        uses: actions/download-artifact@v4
        with:
          name: build-${{ github.sha }}

      - name: SSH ile production deploy
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROD_SSH_HOST }}
          username: ${{ secrets.PROD_SSH_USERNAME }}
          key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
          port: ${{ secrets.PROD_SSH_PORT }}
          script_stop: true
          script: |
            set -e
            
            SITE_DIR="/var/www/production-site"
            BACKUP_DIR="/var/backups/wordpress/$(date +%Y%m%d_%H%M%S)"
            
            echo "=== Backup aliniyor ==="
            mkdir -p $BACKUP_DIR
            cp -r $SITE_DIR/wp-content/themes $BACKUP_DIR/
            cp -r $SITE_DIR/wp-content/plugins $BACKUP_DIR/
            mysqldump -u ${{ secrets.WP_DB_USER }} -p${{ secrets.WP_DB_PASSWORD }} 
              ${{ secrets.WP_DB_NAME }} | gzip > $BACKUP_DIR/database.sql.gz
            
            echo "=== Maintenance mode aktif ==="
            wp maintenance-mode activate --path=$SITE_DIR --allow-root
            
            echo "=== Dosyalar guncelleniyor ==="
            cd $SITE_DIR
            git fetch origin main
            git reset --hard origin/main
            
            echo "=== Composer yukleniyor ==="
            composer install --no-dev --optimize-autoloader
            
            echo "=== Cache temizleniyor ==="
            wp cache flush --path=$SITE_DIR --allow-root
            wp rewrite flush --path=$SITE_DIR --allow-root
            
            echo "=== Maintenance mode kapatiliyor ==="
            wp maintenance-mode deactivate --path=$SITE_DIR --allow-root
            
            echo "=== Eski backuplar temizleniyor (30 gundan eski) ==="
            find /var/backups/wordpress/ -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
            
            echo "Deployment basarili: $(date)"
            echo "Commit: ${{ github.sha }}"

WP-CLI Entegrasyonu

WP-CLI olmadan WordPress otomasyonu düşünülemez. Sunucuya WP-CLI kurulumu:

# WP-CLI kurulumu
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

# Kurulumu dogrula
wp --info

# Deployment scriptinde yararli WP-CLI komutlari
wp core update-db --path=/var/www/site --allow-root
wp plugin activate --all --path=/var/www/site --allow-root
wp cache flush --path=/var/www/site --allow-root
wp cron event run --due-now --path=/var/www/site --allow-root
wp search-replace 'staging.site.com' 'www.site.com' --path=/var/www/site --allow-root

Rollback Mekanizması

Her şeyin ters gideceğini varsayarak planlama yapmak iyi bir sysadmin’in özelliğidir. Ayrı bir rollback workflow’u:

# .github/workflows/rollback.yml
name: Rollback Production

on:
  workflow_dispatch:
    inputs:
      backup_timestamp:
        description: 'Rollback yapilacak backup tarihi (YYYYMMDD_HHMMSS)'
        required: true
      confirm:
        description: 'ROLLBACK yazarak onaylayin'
        required: true

jobs:
  rollback:
    name: Rollback to Previous Version
    runs-on: ubuntu-latest
    environment: production
    
    steps:
      - name: Onay kontrolu
        run: |
          if [ "${{ github.event.inputs.confirm }}" != "ROLLBACK" ]; then
            echo "Onay gecersiz. 'ROLLBACK' yazmaniz gerekiyor."
            exit 1
          fi

      - name: Rollback islemi
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROD_SSH_HOST }}
          username: ${{ secrets.PROD_SSH_USERNAME }}
          key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
          port: ${{ secrets.PROD_SSH_PORT }}
          script: |
            set -e
            
            SITE_DIR="/var/www/production-site"
            BACKUP_DIR="/var/backups/wordpress/${{ github.event.inputs.backup_timestamp }}"
            
            if [ ! -d "$BACKUP_DIR" ]; then
              echo "HATA: Backup dizini bulunamadi: $BACKUP_DIR"
              exit 1
            fi
            
            echo "=== Maintenance mode aktif ==="
            wp maintenance-mode activate --path=$SITE_DIR --allow-root
            
            echo "=== Temalar ve pluginler geri yukleniyor ==="
            rsync -av $BACKUP_DIR/themes/ $SITE_DIR/wp-content/themes/
            rsync -av $BACKUP_DIR/plugins/ $SITE_DIR/wp-content/plugins/
            
            echo "=== Veritabani geri yukleniyor ==="
            gunzip < $BACKUP_DIR/database.sql.gz | mysql -u 
              ${{ secrets.WP_DB_USER }} -p${{ secrets.WP_DB_PASSWORD }} 
              ${{ secrets.WP_DB_NAME }}
            
            echo "=== Cache temizleniyor ==="
            wp cache flush --path=$SITE_DIR --allow-root
            
            echo "=== Maintenance mode kapatiliyor ==="
            wp maintenance-mode deactivate --path=$SITE_DIR --allow-root
            
            echo "Rollback tamamlandi: $BACKUP_DIR"

Slack Bildirimi Entegrasyonu

Deployment’ın başarılı mı yoksa başarısız mı olduğunu anında öğrenmek için Slack entegrasyonu ekleyelim. Bu adımı production workflow’unun sonuna ekliyoruz:

      - name: Basari bildirimi
        if: success()
        uses: slackapi/[email protected]
        with:
          payload: |
            {
              "text": ":rocket: *Production Deployment Basarili!*",
              "attachments": [
                {
                  "color": "good",
                  "fields": [
                    {
                      "title": "Proje",
                      "value": "${{ github.repository }}",
                      "short": true
                    },
                    {
                      "title": "Branch",
                      "value": "${{ github.ref_name }}",
                      "short": true
                    },
                    {
                      "title": "Commit",
                      "value": "${{ github.sha }}",
                      "short": false
                    },
                    {
                      "title": "Yapan",
                      "value": "${{ github.actor }}",
                      "short": true
                    }
                  ]
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

      - name: Hata bildirimi
        if: failure()
        uses: slackapi/[email protected]
        with:
          payload: |
            {
              "text": ":fire: *Production Deployment BASARISIZ!*nHemen kontrol edin: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

Sık Karşılaşılan Sorunlar ve Çözümleri

SSH bağlantısı kurulamıyor: En yaygın sorun. Kontrol listesi şöyle:

  • SSH_PRIVATE_KEY secret’ına private key’in tamamını yapıştırdın mı? -----BEGIN OPENSSH PRIVATE KEY----- ve -----END OPENSSH PRIVATE KEY----- dahil.
  • Sunucudaki ~/.ssh/authorized_keys dosyasında public key doğru mu?
  • ~/.ssh/authorized_keys dosyasının izinleri 600 olmalı: chmod 600 ~/.ssh/authorized_keys
  • Firewall SSH portunu engelliyor olabilir

wp: command not found hatası: WP-CLI PATH’de olmayabilir. SSH üzerinden çalıştırırken tam path kullanmayı dene: /usr/local/bin/wp veya .bashrc‘yi kaynakla: source ~/.bashrc && wp cache flush

Dosya izin sorunları: Deployment sırasında Apache/Nginx kullanıcısı ile deployer kullanıcısı çakışabilir.

# Sunucuda bu ayarlari yap
sudo chown -R deployer:www-data /var/www/site
sudo find /var/www/site -type d -exec chmod 2775 {} ;
sudo find /var/www/site -type f -exec chmod 664 {} ;

Composer memory limit hatası: GitHub Actions runner’da bazen bellek yetersiz kalabiliyor.

# Composer komutuna memory limit ekle
php -d memory_limit=512M /usr/local/bin/composer install --no-dev

Güvenlik İpuçları

Deployment otomasyonunu kurarken güvenliği ihmal etmek büyük hata olur:

  • Secrets rotasyonu: SSH key ve veritabanı şifrelerini düzenli aralıklarla değiştir. 3-6 ayda bir rutin haline getir.
  • Environment koruması: GitHub’da production environment için required reviewers ayarla. Bu sayede birinin onayı olmadan production’a deploy yapılamaz.
  • Branch protection: main branch’e direkt push’u engelle, sadece PR üzerinden merge’e izin ver.
  • Artifact temizliği: retention-days değerini makul tut, gereksiz yere build artifact’larını sonsuza kadar saklama.
  • Deployment kullanıcısı izinleri: deployer kullanıcısına sadece ihtiyaç duyduğu dizinlere erişim ver, sudo yetkisi verme.
  • Log takibi: Deployment loglarını düzenli gözden geçir, başarısız denemeleri takip et.

Sonuç

Bu noktaya kadar geldiysen artık elimde bir WordPress deployment pipeline’ın var demektir. Şimdi develop branch’ine push atınca staging’e, main branch’e merge edince production’a otomatik deploy olacak. Bir şeyler ters giderse Slack’ten anında haber alacaksın ve rollback mekanizman hazır bekliyor olacak.

Başta karmaşık görünüyor ama bir kez kurduğunda neden daha önce yapmadığını soruyorsun kendine. Elle FTP ile dosya atmak, SSH’a girip git pull yazmak… bunların hepsi geride kalıyor.

Bir sonraki adım olarak şunları düşünebilirsin: PHP Unit testlerini workflow’a eklemek, staging ortamında otomatik smoke test çalıştırmak, database migration’larını otomatikleştirmek. Ama bunlar ileri seviye konular. Şimdilik bu temeli sağlam kur, üzerine inşa etmesi çok daha kolay olacak.

Workflow’larını yazarken bir şeyi unutma: GitHub Actions logları oldukça ayrıntılı. Bir şey çalışmıyorsa logları dikkatlice oku, çoğunlukla cevap orada gizlidir.

Bir yanıt yazın

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