WordPress ve MySQL Kurulumu: Adım Adım Docker Compose Rehberi

Docker ile uygulama yönetmeye başladığınızda, er ya da geç en klasik kombinasyona gelip dayanıyorsunuz: WordPress ve MySQL. Bu ikili, Docker Compose öğrenmek için mükemmel bir laboratuvar ortamı sunuyor. Tek bir YAML dosyasıyla iki servisi ayağa kaldırmak, aralarındaki ağı kurmak, veriyi kalıcı hale getirmek ve ortamı yönetilebilir tutmak, gerçek dünya senaryolarının neredeyse tamamını kapsıyor. Bu yazıda sıfırdan başlayıp production’a yakın bir kurulum yapacağız.

Neden Docker Compose?

Manuel Docker komutlarıyla çalışırken şöyle bir senaryo düşünün: MySQL container’ını başlatıyorsunuz, network oluşturuyorsunuz, volume bağlıyorsunuz, environment variable’ları tek tek giriyorsunuz, ardından WordPress için aynı süreci tekrarlıyorsunuz. Her şeyi sıfırdan yapmak yerine, tüm bu adımları docker-compose.yml dosyasına yazıp tek bir komutla çalıştırabilirsiniz.

Docker Compose’un bu senaryo için bize sağladığı avantajlar:

  • Tekrarlanabilirlik: Aynı dosyayı başka bir sunucuya kopyalayıp docker compose up diyorsunuz, ortam aynı şekilde ayağa kalkıyor
  • Servis bağımlılık yönetimi: WordPress’in MySQL hazır olmadan başlamasını engelleyebiliyorsunuz
  • Merkezi konfigürasyon: Tüm ayarlar tek bir yerden yönetiliyor
  • Kolay ölçeklendirme: İhtiyaç duyulduğunda servisleri ölçeklendirmek komut satırından halledilebiliyor

Ön Gereksinimler

Devam etmeden önce sisteminizde şunların kurulu olması gerekiyor:

# Docker versiyonunu kontrol et
docker --version

# Docker Compose versiyonunu kontrol et (modern sürümlerde docker compose şeklinde)
docker compose version

# Alternatif olarak eski standalone sürüm
docker-compose --version

Docker Engine 20.10 ve üzeri versiyonlarda docker compose komutu Docker CLI’a entegre geliyor. Eğer eski bir kurulum varsa docker-compose komutunu kullanmanız gerekebilir. Bu yazıda modern sözdizimiyle devam edeceğiz.

Proje Yapısını Oluşturma

Düzenli bir çalışma ortamı için önce dizin yapısını oluşturalım:

mkdir -p ~/projects/wordpress-compose
cd ~/projects/wordpress-compose

# Proje dosyalarını tutacak alt dizinler
mkdir -p nginx/conf.d
mkdir -p mysql/init
touch docker-compose.yml
touch .env

Projenizin son hali şu yapıda olacak:

wordpress-compose/
├── docker-compose.yml
├── .env
├── nginx/
│   └── conf.d/
│       └── wordpress.conf
└── mysql/
    └── init/
        └── init.sql

.env Dosyası ile Hassas Bilgileri Yönetmek

Şifreler ve kullanıcı adları gibi hassas bilgileri doğrudan docker-compose.yml dosyasına yazmak iyi bir pratik değil. .env dosyası bu sorunu çözüyor ve aynı zamanda farklı ortamlar için (development, staging, production) farklı değerler kullanmanıza imkan tanıyor.

# .env dosyasını düzenle
cat > .env << 'EOF'
# MySQL Ayarları
MYSQL_ROOT_PASSWORD=guclu_root_sifresi_2024
MYSQL_DATABASE=wordpress_db
MYSQL_USER=wp_user
MYSQL_PASSWORD=guclu_wp_sifresi_2024

# WordPress Ayarları
WORDPRESS_TABLE_PREFIX=wp_
WORDPRESS_DEBUG=false

# Genel Ayarlar
COMPOSE_PROJECT_NAME=wordpress_site
EOF

.env dosyasını mutlaka .gitignore‘a eklemeyi unutmayın:

echo ".env" >> .gitignore
echo "mysql/data/" >> .gitignore

Temel docker-compose.yml Dosyası

Önce çalışan minimal bir versiyon yazalım, ardından üzerine eklemeler yapacağız:

# docker-compose.yml - Temel Versiyon
version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: wordpress_mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d
    networks:
      - wordpress_network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    image: wordpress:6.4-php8.2-fpm
    container_name: wordpress_app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
      WORDPRESS_DEBUG: ${WORDPRESS_DEBUG}
    volumes:
      - wordpress_data:/var/www/html
    networks:
      - wordpress_network

volumes:
  mysql_data:
    driver: local
  wordpress_data:
    driver: local

networks:
  wordpress_network:
    driver: bridge

Bu dosyada dikkat etmemiz gereken birkaç önemli nokta var:

  • restart: unless-stopped: Container elle durdurulmadıkça sistem yeniden başladığında container da otomatik başlıyor
  • depends_on ile condition: service_healthy: WordPress, MySQL tamamen hazır olmadan başlamıyor. Bu sayede “database connection error” hatasından kurtuluyorsunuz
  • healthcheck: MySQL’in gerçekten hazır olup olmadığını düzenli aralıklarla kontrol ediyor
  • wordpress:6.4-php8.2-fpm: FPM (FastCGI Process Manager) versiyonu, Nginx ile birlikte kullanım için daha performanslı

Nginx’i Reverse Proxy Olarak Eklemek

PHP-FPM kullanan WordPress image’ı kendi başına HTTP sunucusu olarak çalışmıyor. Önüne bir Nginx veya Apache koymanız gerekiyor. Production ortamı için Nginx daha iyi bir tercih:

# Nginx konfigürasyonunu oluştur
cat > nginx/conf.d/wordpress.conf << 'EOF'
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    client_max_body_size 64M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ .php$ {
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires max;
        log_not_found off;
        access_log off;
    }

    location ~ /. {
        deny all;
    }
}
EOF

Şimdi docker-compose.yml dosyasına Nginx servisini ekleyelim:

# docker-compose.yml - Nginx Eklenmiş Versiyon
version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: wordpress_mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d
    networks:
      - wordpress_network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    image: wordpress:6.4-php8.2-fpm
    container_name: wordpress_app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
    volumes:
      - wordpress_data:/var/www/html
    networks:
      - wordpress_network

  nginx:
    image: nginx:alpine
    container_name: wordpress_nginx
    restart: unless-stopped
    ports:
      - "80:80"
    depends_on:
      - wordpress
    volumes:
      - wordpress_data:/var/www/html:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
    networks:
      - wordpress_network

volumes:
  mysql_data:
    driver: local
  wordpress_data:
    driver: local

networks:
  wordpress_network:
    driver: bridge

Burada wordpress_data volume’unun hem WordPress hem de Nginx tarafından paylaşılmasına dikkat edin. Nginx, statik dosyaları doğrudan bu volume’dan sunuyor, PHP dosyaları için ise WordPress/FPM container’ına yönlendiriyor. Nginx bu volume’u ro (read-only) olarak bağlıyor, güvenlik açısından doğru yaklaşım bu.

Kurulumu Başlatmak ve İlk Test

Her şey hazır olduğuna göre ortamı ayağa kaldıralım:

# Detached modda başlat
docker compose up -d

# Logları takip et
docker compose logs -f

# Sadece belirli bir servisin loglarını görmek için
docker compose logs -f wordpress
docker compose logs -f db

Container’ların durumunu kontrol edelim:

# Tüm servislerin durumu
docker compose ps

# Container sağlık durumu
docker inspect wordpress_mysql | grep -A 10 '"Health"'

Tarayıcıdan http://localhost adresine gittiğinizde WordPress kurulum sihirbazıyla karşılaşmalısınız. Karşılaşmıyorsanız logları incelemeniz gerekiyor.

MySQL’e Bağlanma ve Veritabanı Yönetimi

Container içindeki MySQL’e bağlanmak için:

# MySQL container'ına doğrudan bağlan
docker compose exec db mysql -u ${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE}

# Root olarak bağlanmak için
docker compose exec db mysql -u root -p${MYSQL_ROOT_PASSWORD}

# Tek seferlik sorgu çalıştırmak
docker compose exec db mysql -u root -p${MYSQL_ROOT_PASSWORD} -e "SHOW DATABASES;"

# WordPress veritabanı tablolarını listele
docker compose exec db mysql -u ${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE} -e "SHOW TABLES;"

Production’da adminer veya phpMyAdmin gibi bir web arayüzü eklemek isteyebilirsiniz. Geliştirme ortamı için Adminer oldukça hafif bir çözüm:

# docker-compose.override.yml - Sadece development için
version: '3.8'

services:
  adminer:
    image: adminer:latest
    container_name: wordpress_adminer
    restart: unless-stopped
    ports:
      - "8080:8080"
    networks:
      - wordpress_network
    environment:
      ADMINER_DEFAULT_SERVER: db

docker-compose.override.yml dosyası otomatik olarak ana docker-compose.yml ile birleştiriliyor. Production’da bu dosyayı görmezden gelmek için docker compose -f docker-compose.yml up -d şeklinde çalıştırabilirsiniz.

Yedekleme Stratejisi

Container tabanlı bir WordPress kurulumunda en kritik konulardan biri yedekleme. İki şeyi yedeklemeniz gerekiyor: veritabanı ve dosyalar.

#!/bin/bash
# backup.sh - WordPress ve MySQL yedekleme scripti

BACKUP_DIR="/opt/backups/wordpress"
DATE=$(date +%Y%m%d_%H%M%S)
PROJECT_DIR="/home/user/projects/wordpress-compose"

mkdir -p ${BACKUP_DIR}

# .env dosyasından değerleri yükle
source ${PROJECT_DIR}/.env

echo "[$(date)] Yedekleme başlıyor..."

# MySQL yedeği
echo "[$(date)] Veritabanı yedekleniyor..."
docker compose -f ${PROJECT_DIR}/docker-compose.yml exec -T db 
  mysqldump -u root -p${MYSQL_ROOT_PASSWORD} 
  --single-transaction 
  --routines 
  --triggers 
  ${MYSQL_DATABASE} | gzip > ${BACKUP_DIR}/mysql_${DATE}.sql.gz

# WordPress dosyaları yedeği
echo "[$(date)] Dosyalar yedekleniyor..."
docker run --rm 
  -v wordpress_compose_wordpress_data:/source:ro 
  -v ${BACKUP_DIR}:/backup 
  alpine tar czf /backup/wordpress_files_${DATE}.tar.gz -C /source .

# 7 günden eski yedekleri sil
find ${BACKUP_DIR} -name "*.gz" -mtime +7 -delete

echo "[$(date)] Yedekleme tamamlandı: ${BACKUP_DIR}"

Script’i çalıştırılabilir yapıp cron’a ekleyelim:

chmod +x backup.sh

# Cron job ekle - her gece saat 02:00'de
crontab -e
# Şunu ekle:
# 0 2 * * * /home/user/projects/wordpress-compose/backup.sh >> /var/log/wp-backup.log 2>&1

Veri Geri Yükleme

Yedekten geri yükleme senaryosu da en az yedekleme kadar önemli:

# MySQL yedeğini geri yükle
gunzip < /opt/backups/wordpress/mysql_20241201_020000.sql.gz | 
  docker compose exec -T db 
  mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DATABASE}

# WordPress dosyalarını geri yükle
docker run --rm 
  -v wordpress_compose_wordpress_data:/target 
  -v /opt/backups/wordpress:/backup 
  alpine sh -c "cd /target && tar xzf /backup/wordpress_files_20241201_020000.tar.gz"

Performans İyileştirmeleri

Gerçek dünya kullanımı için birkaç önemli ayar daha yapalım. PHP-FPM ve MySQL için özel konfigürasyon dosyaları oluşturacağız:

# PHP-FPM için özel konfigürasyon
mkdir -p wordpress/php-fpm

cat > wordpress/php-fpm/custom.ini << 'EOF'
; PHP Bellek ve Performans Ayarları
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
post_max_size = 64M
upload_max_filesize = 64M
max_input_vars = 3000

; OPcache - Production performansı için kritik
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
opcache.revalidate_freq = 60
opcache.fast_shutdown = 1
EOF

Bu konfigürasyonu WordPress servisine bağlamak için docker-compose.yml dosyasındaki WordPress servisine şu volume satırını eklemeniz yeterli:

volumes:
  - wordpress_data:/var/www/html
  - ./wordpress/php-fpm/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro

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

MySQL başlamıyor veya WordPress bağlanamıyor

# MySQL loglarını kontrol et
docker compose logs db

# Healthcheck durumunu gör
docker inspect wordpress_mysql --format='{{.State.Health.Status}}'

# MySQL container'ının içine gir ve manuel test yap
docker compose exec db bash
mysqladmin ping -h localhost -u root -pROOT_SIFRESI

Permission hataları

WordPress dosyaları bazen yanlış ownership ile oluşuluyor. Bunu düzeltmek için:

# WordPress container'ı içinde dosya sahipliğini düzelt
docker compose exec wordpress chown -R www-data:www-data /var/www/html
docker compose exec wordpress find /var/www/html -type d -exec chmod 755 {} ;
docker compose exec wordpress find /var/www/html -type f -exec chmod 644 {} ;

Volume’lar arasında veri görünmüyor

Nginx’in WordPress dosyalarını göremediği durumda volume mount sırasını kontrol edin. docker compose down yapıp docker compose up -d yeniden çalıştırın. Volume’un adını doğru yazdığınızdan emin olun:

# Mevcut volume'ları listele
docker volume ls | grep wordpress

# Volume detaylarını incele
docker volume inspect wordpress_compose_wordpress_data

Güncelleme Prosedürü

Container tabanlı kurulumun en büyük avantajlarından biri güncelleme kolaylığı. Ancak bunu doğru yapmak önemli:

# Mevcut image versiyonlarını not et
docker compose images

# Yeni image'ları çek (container'ları durdurmadan)
docker compose pull

# Zero-downtime update için önce yedek al
./backup.sh

# Servisleri yeniden oluştur
docker compose up -d --force-recreate

# Eski image'ları temizle
docker image prune -f

WordPress veya MySQL major versiyon güncellemelerinde daha dikkatli olmak gerekiyor. MySQL 5.7’den 8.0’a geçiş özellikle veritabanı migration gerektiriyor. Böyle bir durumda önce yedeği alın, test ortamında deneyin, ardından production’a uygulayın.

Sonuç

Docker Compose ile WordPress ve MySQL kurulumu başlangıçta karmaşık görünse de adım adım ilerlediğinizde her parçanın mantıklı bir yerde durduğunu görüyorsunuz. .env dosyasıyla hassas bilgileri ayırmak, healthcheck ile servis bağımlılıklarını doğru yönetmek ve düzenli yedekleme scripti yazmak bu kurulumun üç temel direği.

Burada anlattığımız yapı birçok küçük ve orta ölçekli WordPress sitesi için yeterli. Daha yüksek trafik için Redis cache eklemek, veritabanı replikasyonu kurmak veya Nginx önüne bir CDN koymak gibi adımlarla sistemi büyütebilirsiniz. Ancak bu temel yapı sağlam olmadan üstüne inşa etmek her zaman sorunlara davet çıkarmak demek.

En önemli tavsiyem: Bu kurulumu production’a almadan önce mutlaka farklı bir makinede test edin ve yedek/geri yükleme süreçlerini prova yapın. Felaketi simüle etmeden hazır olduğunuzu düşünmek, sysadmin hayatının en tehlikeli tuzaklarından biri.

Yorum yapın