Docker ile PHP Geliştirme Ortamı Kurulumu
Yerel makinende PHP geliştirme yaparken “bende çalışıyor ama sunucuda çalışmıyor” sorunuyla kaç kez karşılaştın? Ya da farklı projeler için farklı PHP sürümleri gerektiren bir durumla? Docker, tam da bu noktada hayat kurtarıcı oluyor. Geliştirme ortamını container içine hapsettiğinde, PHP sürümü ne olursa olsun, hangi kütüphane kurulu olursa olsun, her şey izole ve tekrarlanabilir hale geliyor.
Bu yazıda sıfırdan bir Docker tabanlı PHP geliştirme ortamı kuracağız. PHP-FPM, Nginx, MySQL ve Redis’i birlikte ayağa kaldıracak, Composer entegrasyonunu yapacak ve gerçek dünya senaryolarına uygun bir yapı oluşturacağız. Hem PHP 7.4 hem de PHP 8.2 gibi farklı sürümleri aynı makinede nasıl yöneteceğini de göreceğiz.
Neden Docker ile PHP Geliştirme?
Klasik yaklaşımda XAMPP ya da WAMP kurarsın, sistem geneline PHP yüklersin ve bir süre sonra her şey birbirine girer. Bir proje PHP 7.4 ister, diğeri PHP 8.1 ister. phpbrew veya phpenv gibi araçlarla idare etmeye çalışırsın ama yine de sistem bağımlılıkları seni yorar.
Docker’ın getirdiği avantajlar şunlar:
- İzolasyon: Her proje kendi container’ında çalışır, birbirini etkilemez
- Tekrarlanabilirlik:
docker-compose upkomutuyla tüm ekip aynı ortamı ayağa kaldırır - Temiz sistem: Yerel makinene hiçbir şey kurmak zorunda kalmazsın
- Sürüm esnekliği: PHP 7.4’ten 8.3’e geçiş bir satır değişikliği
- Prodüksiyon yakınlığı: Geliştirme ortamını production’a olabildiğince yakın tutarsın
Proje Yapısını Oluşturalım
Önce klasör yapısını netleştirelim. Düzenli bir yapı, ilerleyen süreçte çok işine yarar:
mkdir php-docker-dev && cd php-docker-dev
mkdir -p {src,docker/{nginx,php,mysql},logs/{nginx,php}}
touch docker-compose.yml
touch docker/nginx/default.conf
touch docker/php/Dockerfile
touch docker/php/php.ini
touch .env
touch src/index.php
Ortaya şöyle bir yapı çıkmalı:
php-docker-dev/
├── docker-compose.yml
├── .env
├── src/
│ └── index.php
├── docker/
│ ├── nginx/
│ │ └── default.conf
│ ├── php/
│ │ ├── Dockerfile
│ │ └── php.ini
│ └── mysql/
│ └── init.sql
└── logs/
├── nginx/
└── php/
.env Dosyasını Yapılandırma
Hassas bilgileri ve değişebilir parametreleri .env dosyasında tutmak iyi bir alışkanlık. Böylece aynı docker-compose.yml dosyasını farklı ortamlar için kullanabilirsin:
# .env dosyası
# PHP Sürümü
PHP_VERSION=8.2
# MySQL
MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_DATABASE=myapp
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword123
# Port Yönlendirmeleri
NGINX_PORT=8080
MYSQL_PORT=3306
REDIS_PORT=6379
PHPMYADMIN_PORT=8081
# Ortam
APP_ENV=development
PHP Dockerfile Hazırlama
Burada kritik bir karar vermek gerekiyor: resmi php:8.2-fpm image’ını mı kullanalım, yoksa özelleştirilmiş bir image mı oluşturalım? Gerçek projelerde genellikle özelleştirilmiş bir Dockerfile şart oluyor çünkü ihtiyaç duyduğun extension’ları ve araçları kurman gerekiyor.
# docker/php/Dockerfile
ARG PHP_VERSION=8.2
FROM php:${PHP_VERSION}-fpm
# Sistem bağımlılıklarını kur
RUN apt-get update && apt-get install -y
git
curl
libpng-dev
libonig-dev
libxml2-dev
libzip-dev
libpq-dev
libredis-dev
zip
unzip
vim
&& apt-get clean
&& rm -rf /var/lib/apt/lists/*
# PHP extension'larını kur
RUN docker-php-ext-install
pdo
pdo_mysql
pdo_pgsql
mbstring
exif
pcntl
bcmath
gd
zip
opcache
# Redis extension'ı PECL üzerinden kur
RUN pecl install redis
&& docker-php-ext-enable redis
# Composer'ı kur
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Çalışma dizinini ayarla
WORKDIR /var/www/html
# PHP kullanıcısı ile çalış
RUN usermod -u 1000 www-data
USER www-data
Bu Dockerfile’da dikkat edilmesi gereken birkaç nokta var. usermod -u 1000 www-data satırı önemli çünkü Linux sistemlerde varsayılan kullanıcı UID’si genellikle 1000. Eğer container içindeki dosya izinleri ile host sistemin izinleri uyuşmazsa, düzenlediğin dosyalara okuma/yazma sorunu yaşarsın.
PHP Yapılandırması
Geliştirme ortamı için PHP’yi biraz gevşetmek gerekiyor. Hata gösterimi, bellek limiti ve upload boyutu gibi ayarları düzenleyelim:
# docker/php/php.ini
[PHP]
; Hata Yönetimi
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On
error_log = /var/log/php/error.log
; Bellek ve Performans
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
; Dosya Upload
upload_max_filesize = 64M
post_max_size = 64M
max_file_uploads = 20
; Timezone
date.timezone = Europe/Istanbul
; OPcache (Geliştirme için kapalı tutabiliriz)
opcache.enable = 0
opcache.enable_cli = 0
; Session
session.gc_maxlifetime = 1440
session.save_path = /tmp
[xdebug]
; Xdebug ayarları (opsiyonel)
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_host = host.docker.internal
xdebug.client_port = 9003
Nginx Yapılandırması
PHP-FPM ile Nginx’i birlikte kullanmak en yaygın production yapısı. Geliştirme ortamında da aynı yapıyı kullanmak mantıklı:
# docker/nginx/default.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
# Erişim ve hata logları
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Genel location
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP dosyalarını FPM'e gönder
location ~ .php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Statik dosyalar için cache
location ~* .(jpg|jpeg|gif|png|css|js|ico|xml)$ {
expires 5d;
}
# .htaccess erişimini engelle
location ~ /.ht {
deny all;
}
# Gzip sıkıştırma
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 256;
}
Docker Compose ile Her Şeyi Bir Araya Getirme
Şimdi asıl yapıyı oluşturuyoruz. docker-compose.yml dosyası tüm servisleri tanımlıyor ve birbirleriyle nasıl konuşacaklarını belirliyor:
# docker-compose.yml
version: '3.8'
services:
# PHP-FPM Servisi
php:
build:
context: ./docker/php
dockerfile: Dockerfile
args:
PHP_VERSION: ${PHP_VERSION:-8.2}
container_name: php_app
restart: unless-stopped
volumes:
- ./src:/var/www/html
- ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
- ./logs/php:/var/log/php
environment:
- APP_ENV=${APP_ENV:-development}
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=${MYSQL_DATABASE}
- DB_USERNAME=${MYSQL_USER}
- DB_PASSWORD=${MYSQL_PASSWORD}
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- app_network
# Nginx Web Sunucusu
nginx:
image: nginx:alpine
container_name: nginx_server
restart: unless-stopped
ports:
- "${NGINX_PORT:-8080}:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./logs/nginx:/var/log/nginx
depends_on:
- php
networks:
- app_network
# MySQL Veritabanı
mysql:
image: mysql:8.0
container_name: mysql_db
restart: unless-stopped
ports:
- "${MYSQL_PORT:-3306}:3306"
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
- ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
networks:
- app_network
# Redis Cache
redis:
image: redis:7-alpine
container_name: redis_cache
restart: unless-stopped
ports:
- "${REDIS_PORT:-6379}:6379"
command: redis-server --appendonly yes --requirepass ""
volumes:
- redis_data:/data
networks:
- app_network
# phpMyAdmin (Opsiyonel)
phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin
restart: unless-stopped
ports:
- "${PHPMYADMIN_PORT:-8081}:80"
environment:
PMA_HOST: mysql
PMA_PORT: 3306
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
depends_on:
- mysql
networks:
- app_network
volumes:
mysql_data:
driver: local
redis_data:
driver: local
networks:
app_network:
driver: bridge
İlk Uygulama Dosyasını Oluşturma
Test için basit bir PHP dosyası hazırlayalım. Gerçek uygulamalarda public/index.php şeklinde bir yapı kullanırsın ama şimdi doğrudan src/index.php olarak ekleyelim:
<?php
// src/index.php
phpinfo();
// Veritabanı bağlantı testi
try {
$pdo = new PDO(
sprintf(
"mysql:host=%s;port=%s;dbname=%s",
getenv('DB_HOST'),
getenv('DB_PORT'),
getenv('DB_DATABASE')
),
getenv('DB_USERNAME'),
getenv('DB_PASSWORD'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
echo "<p style='color:green'>MySQL bağlantısı başarılı!</p>";
} catch (PDOException $e) {
echo "<p style='color:red'>MySQL bağlantı hatası: " . $e->getMessage() . "</p>";
}
// Redis bağlantı testi
try {
$redis = new Redis();
$redis->connect(getenv('REDIS_HOST'), getenv('REDIS_PORT'));
$redis->set('test_key', 'Docker ile PHP çalışıyor!');
$value = $redis->get('test_key');
echo "<p style='color:green'>Redis bağlantısı başarılı: " . $value . "</p>";
} catch (Exception $e) {
echo "<p style='color:red'>Redis hatası: " . $e->getMessage() . "</p>";
}
Ortamı Ayağa Kaldırma
Artık her şey hazır. Şu komutlarla ortamı başlatabilirsin:
# İlk kez image'ları oluştur ve container'ları başlat
docker compose up -d --build
# Container durumlarını kontrol et
docker compose ps
# Logları takip et
docker compose logs -f
# Sadece belirli bir servisin logunu gör
docker compose logs -f php
Tarayıcıdan http://localhost:8080 adresine girdiğinde PHP info sayfasını görmelisin. Sorun yaşarsan önce container loglarına bak:
# Nginx loglarını kontrol et
docker compose logs nginx
# PHP-FPM loglarını kontrol et
docker compose logs php
# MySQL'in hazır olup olmadığını kontrol et
docker compose exec mysql mysqladmin ping -h localhost
Günlük Kullanım ve Pratik Komutlar
Docker ile PHP geliştirirken en çok kullanacağın komutları bir araya topladım:
# Container içinde Composer komutlarını çalıştır
docker compose exec php composer install
docker compose exec php composer require laravel/framework
# Artisan veya diğer CLI araçlarını çalıştır
docker compose exec php php artisan migrate
docker compose exec php php artisan cache:clear
# Container içine bash ile gir
docker compose exec php bash
# PHP sürümünü doğrula
docker compose exec php php --version
# Container'ları durdur (veriyi koru)
docker compose stop
# Container'ları ve ağı kaldır (veriyi koru)
docker compose down
# Her şeyi sıfırla (veritabanı dahil)
docker compose down -v
Farklı PHP Sürümleri Arasında Geçiş
İşte Docker’ın en güzel özelliklerinden biri bu. Bir projeyi PHP 7.4 ile geliştirip diğerini PHP 8.2 ile çalıştırmak çok basit:
# PHP sürümünü değiştirmek için .env dosyasını güncelle
# ya da doğrudan komut satırından override et
PHP_VERSION=7.4 docker compose up -d --build
# Ya da kalıcı olarak .env'i güncelle
sed -i 's/PHP_VERSION=8.2/PHP_VERSION=7.4/' .env
docker compose up -d --build
# Değişikliği doğrula
docker compose exec php php --version
Birden fazla projen varsa her biri için ayrı bir klasör yapısı oluşturabilirsin. Port çakışmalarından kaçınmak için .env dosyasındaki portları farklı ayarla:
# Proje A - PHP 8.2
# NGINX_PORT=8080
# MYSQL_PORT=3306
# Proje B - PHP 7.4
# NGINX_PORT=8090
# MYSQL_PORT=3307
Laravel veya Symfony Projesi Eklemek
Mevcut bir framework projesini bu yapıya entegre etmek istersen:
# Yeni Laravel projesi oluştur
docker compose exec php composer create-project laravel/laravel /var/www/html --prefer-dist
# Ya da mevcut projeyi src klasörüne kopyala
cp -r /mevcut/proje/* ./src/
# Bağımlılıkları yükle
docker compose exec php composer install
# Laravel için .env ayarlarını yap
docker compose exec php cp .env.example .env
docker compose exec php php artisan key:generate
# Veritabanını oluştur
docker compose exec php php artisan migrate --seed
Nginx yapılandırmasını Laravel için güncellemen gerekecek. default.conf dosyasındaki root satırını şöyle değiştir:
root /var/www/html/public;
Xdebug Entegrasyonu
Gerçek bir geliştirme ortamında Xdebug olmadan yaşamak çok zor. docker/php/Dockerfile dosyasına şu satırları ekleyebilirsin:
# Xdebug'ı sadece development ortamında kur
ARG INSTALL_XDEBUG=false
RUN if [ "$INSTALL_XDEBUG" = "true" ]; then
pecl install xdebug &&
docker-php-ext-enable xdebug;
fi
docker-compose.yml içinde build args olarak geç:
php:
build:
context: ./docker/php
dockerfile: Dockerfile
args:
PHP_VERSION: ${PHP_VERSION:-8.2}
INSTALL_XDEBUG: "true"
VS Code kullanıyorsan launch.json dosyana şu yapılandırmayı ekle:
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker PHP Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}/src"
}
}
]
}
Performans İpuçları
Docker’ın macOS ve Windows’ta Linux kadar hızlı çalışmadığını fark edeceksin, özellikle volume mount işlemlerinde. Performansı artırmak için birkaç öneri:
- Delegated mount kullanımı: Mac’te
cachedveyadelegatedseçenekleri eskiden işe yarıyordu, yeni Docker sürümlerinde varsayılan olarak optimize edilmiş durumda - node_modules dışarıda tut: JavaScript projeleriyle çalışıyorsan
node_modulesiçin named volume kullan, sync yükünü azaltır - OPcache’i production’da aktif et: Geliştirmede kapalı tutsan da test amaçlı production ayarlarını deneyebilirsin
# node_modules için performans optimizasyonu
volumes:
- ./src:/var/www/html
- /var/www/html/node_modules # Bu satır node_modules'u container içinde tutar
Sonuç
Docker tabanlı bir PHP geliştirme ortamı kurmak başta karmaşık görünse de bir kez oturttuğunda hayatını inanılmaz kolaylaştırıyor. Artık “bende çalışıyor” problemi yok, PHP sürüm değişikliği tek satır, yeni ekip üyesi docker compose up --build komutuyla beş dakikada hazır.
Bu yapıyı kendi projelerine uyarlarken birkaç şeyi aklında tut:
- Prodüksiyon ortamına yakın ama geliştirme için optimize edilmiş bir yapı hedefle
- Hassas bilgileri her zaman
.envdosyasında tut, asladocker-compose.ymliçine gömme .envdosyasını.gitignore‘a ekle,.env.exampleile şablonu paylaş- Volume’ları düzenli temizle, disk alanı zamanla dolabilir:
docker system prune -a
PHP sürüm yönetimi açısından Docker, phpbrew veya phpenv gibi araçların önüne geçiyor. Sistem geneline hiçbir şey kurmadan, birbirinden tamamen izole ortamlarda istediğin PHP sürümüyle çalışabiliyorsun. Geliştirme ortamını bir kez doğru kurduğunda, asıl işin olan kod yazmaya odaklanabiliyorsun.
