Docker & DevOps

    Docker ile Laravel Geliştirme Ortamı

    Laravel projesini Docker üzerinde çalıştırmak için gereken servisler, imaj ve günlük iş akışı.

    9 dk okuma Güncellendi: 25 Ağustos 2026

    Bir Laravel projesine yeni katılan geliştiricinin ilk günü genellikle kod yazmakla değil, ortam kurmakla geçer: doğru PHP sürümü, eksik intl eklentisi, farklı MySQL sürümü, Redis'in kurulu olmaması, storage klasörünün izinleri. Üstelik bu kurulum makineden makineye değişir ve "bende çalışıyordu" cümlesi doğar. Docker ile Laravel geliştirme ortamı kurmanın asıl kazancı hız değil, tekrarlanabilirliktir: projeyi klonlayan herkes tek komutla aynı PHP sürümüne, aynı eklenti setine ve aynı veritabanı sürümüne kavuşur.

    Bu rehberde Laravel için dört servisli bir geliştirme ortamı kuracağız — uygulama (PHP-FPM), web sunucusu (Nginx), veritabanı (MySQL) ve önbellek/kuyruk (Redis). Kendi PHP imajımızı Laravel'in gerçekten istediği eklentilerle derleyecek, artisan ve composer komutlarını konteyner içinden nasıl çalıştıracağımızı, kuyruk işçisini nasıl ayrı servis yapacağımızı ve storage izin hatalarının kökünü nasıl kazıyacağımızı göreceğiz. Üretime taşırken nelerin değişmesi gerektiğine de ayrı bir bölüm ayırdım.

    Laravel'i Docker'a Taşırken Neyi Çözmeye Çalışıyoruz#

    Laravel'in çalışması için PHP tek başına yetmez. Framework belirli eklentileri şart koşar (mbstring, openssl, pdo, tokenizer, xml, ctype, json, bcmath, fileinfo), yaygın paketler bunlara intl, gd, zip, redis ekler. Bunları her geliştirici makinesinde elle kurmak yerine bir Dockerfile'a yazarsınız ve bir daha konuşulmaz.

    İkinci problem servis çeşitliliğidir. Modern bir Laravel projesi genelde bir veritabanı, bir önbellek katmanı, bir kuyruk sürücüsü ve sıklıkla bir e-posta yakalayıcı ister. Bunları tek tek kurup yapılandırmak yerine compose dosyasında tanımlarsınız. Üçüncü problem ise sürüm sabitleme: üretimde MySQL 8 çalışırken geliştirmede MariaDB kullanmak, ancak canlıya çıkınca fark edilen sorgu farklarına yol açar.

    SorunDocker'sız durumDocker ile
    PHP eklentileriHer makinede elle kurulurDockerfile'da bir kez tanımlanır
    Veritabanı sürümüGeliştiricinin makinesine bağlıCompose'da sabitlenir
    Redis / kuyrukAyrı kurulum gerekirTek satır servis
    Yeni geliştiriciYarım gün kurulumdocker compose up -d
    Projeyi silmekSistemde artıklar kalırKonteyner ve hacim silinir

    Nginx ve PHP-FPM ikilisinin nasıl konuştuğunu, SCRIPT_FILENAME yol eşleşmesinin neden kritik olduğunu daha önce Docker'da Nginx ve PHP-FPM yığını yazısında ayrıntılı ele almıştık; buradaki yapı onun Laravel'e uyarlanmış hâlidir.

    Proje Yapısı ve PHP İmajı#

    Laravel projesinin kök dizininde docker adında bir klasör açıp tüm altyapı dosyalarını orada toplayın; böylece uygulama kodu ile ortam tanımı karışmaz:

    cd ~/projeler/laravel-uygulama
    mkdir -p docker/{php,nginx}
    

    docker/php/Dockerfile dosyası ortamın belkemiğidir. Burada hem eklentileri kuruyor hem de host kullanıcınızla aynı UID'ye sahip bir kullanıcı oluşturuyoruz — izin sorunlarının kalıcı çözümü budur:

    FROM php:8-fpm-alpine
    
    # Çalışma zamanı ve derleme bağımlılıkları
    RUN apk add --no-cache libpng libzip icu-libs oniguruma git bash \
     && apk add --no-cache --virtual .build $PHPIZE_DEPS libpng-dev libzip-dev icu-dev oniguruma-dev \
     && docker-php-ext-install -j"$(nproc)" pdo_mysql mbstring bcmath gd zip intl exif pcntl opcache \
     && pecl install redis && docker-php-ext-enable redis \
     && apk del .build
    
    COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
    
    # HOST kullanıcınızla aynı UID: dosya sahipliği sorunlarını kökten çözer
    ARG UID=1000
    ARG GID=1000
    RUN addgroup -g ${GID} app && adduser -u ${UID} -G app -s /bin/bash -D app
    
    WORKDIR /var/www/html
    USER app
    

    pcntl eklentisi kuyruk işçisi ve artisan sinyalleri için, exif görsel işleme için gereklidir; ikisi de sonradan "neden çalışmıyor" diye aranan tipik eksiklerdir. UID değerini kendi kullanıcınızdan alacağız, bunu birazdan compose dosyasında göreceksiniz. Bu tekniğin genel gerekçelerini Dockerfile en iyi pratikler yazısında topladık.

    Nginx tarafı Laravel için standarttır; kök dizin public klasörüdür, geri kalan her şey index.php üzerinden geçer:

    server {
        listen 80;
        server_name localhost;
        root /var/www/html/public;
        index index.php;
    
        client_max_body_size 64m;
    
        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }
    
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_pass app:9000;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_read_timeout 300;
        }
    
        location ~ /\. { deny all; }
    }
    

    docker-compose: Dört Servisli Ortam#

    Şimdi servisleri birleştirelim. queue servisinin app ile aynı imajı kullandığına ama farklı komut çalıştırdığına dikkat edin; bu, aynı kod tabanını iki farklı rolde koşturmanın standart yoludur:

    services:
      app:
        build:
          context: ./docker/php
          args:
            UID: ${UID:-1000}
            GID: ${GID:-1000}
        restart: unless-stopped
        volumes:
          - ./:/var/www/html
        depends_on:
          - mysql
          - redis
    
      web:
        image: nginx:alpine
        restart: unless-stopped
        ports:
          - "8080:80"
        volumes:
          - ./:/var/www/html
          - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
        depends_on:
          - app
    
      mysql:
        image: mysql:8
        restart: unless-stopped
        environment:
          MYSQL_ROOT_PASSWORD: root-parolasi
          MYSQL_DATABASE: laravel
          MYSQL_USER: laravel
          MYSQL_PASSWORD: laravel-parolasi
        volumes:
          - mysql-data:/var/lib/mysql
        ports:
          # Yalnızca localhost'a açık: DB istemcinizle bağlanmak için
          - "127.0.0.1:3307:3306"
    
      redis:
        image: redis:alpine
        restart: unless-stopped
    
      queue:
        build:
          context: ./docker/php
          args:
            UID: ${UID:-1000}
            GID: ${GID:-1000}
        restart: unless-stopped
        command: php artisan queue:work --tries=3 --timeout=90
        volumes:
          - ./:/var/www/html
        depends_on:
          - mysql
          - redis
    
    volumes:
      mysql-data:
    

    MySQL portunu 127.0.0.1:3307 biçiminde yayımlamak iki işe yarar: masaüstü veritabanı istemcinizle bağlanabilirsiniz ama port yalnızca yerel makineye açık kalır, ağdaki başka kimse göremez. Sunucuda çalışıyorsanız bu satırı tamamen kaldırın.

    Laravel'in .env dosyasında ana makine adları konteyner adlarıyla değiştirilmelidir; localhost yazarsanız uygulama kendi konteynerine bağlanmaya çalışır ve bağlantı reddedilir:

    APP_URL=http://localhost:8080
    
    DB_CONNECTION=mysql
    DB_HOST=mysql
    DB_PORT=3306
    DB_DATABASE=laravel
    DB_USERNAME=laravel
    DB_PASSWORD=laravel-parolasi
    
    REDIS_HOST=redis
    CACHE_DRIVER=redis
    QUEUE_CONNECTION=redis
    SESSION_DRIVER=redis
    

    İlk Çalıştırma: Bağımlılıklar, Anahtar ve Göç#

    Ortamı ilk kez ayağa kaldırırken sıra önemlidir. UID değişkenini kabuktan geçirerek imajı kendi kullanıcı numaranızla derliyoruz:

    # 1. Kullanıcı numaralarını dışa aktar (bir kez .bashrc'ye de eklenebilir)
    export UID=$(id -u) GID=$(id -g)
    
    # 2. İmajları derle ve servisleri başlat
    docker compose up -d --build
    
    # 3. Bağımlılıkları konteyner içinde çöz
    docker compose exec app composer install
    
    # 4. Ortam dosyası ve uygulama anahtarı
    cp .env.example .env
    docker compose exec app php artisan key:generate
    
    # 5. Veritabanı hazır olana kadar bekleyip göçleri çalıştır
    docker compose exec app php artisan migrate --seed
    
    # 6. Depolama bağlantısı
    docker compose exec app php artisan storage:link
    

    Üçüncü adımda composer install komutunu host makinenizde değil konteyner içinde çalıştırmanız önemlidir. Konteynerdeki PHP sürümü ve eklenti seti farklıysa, host'ta çözülen bağımlılıklar konteynerde çalışmayabilir; bu, geliştirme makinesinde sorunsuz görünüp sunucuda patlayan hataların en yaygın kaynağıdır.

    Beşinci adımda "connection refused" alırsanız MySQL henüz başlangıç sürecini bitirmemiştir. depends_on yalnızca başlama sırasını belirler, servisin hazır olmasını beklemez. Basit çözüm birkaç saniye bekleyip komutu tekrarlamaktır; kalıcı çözüm ise sağlık kontrolü tanımlamaktır:

      mysql:
        healthcheck:
          test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
          interval: 5s
          retries: 10
    

    Günlük Geliştirme Akışı#

    Konteyner içinde komut çalıştırmak zamanla yorucu olur; kabuk takma adları bu yükü tamamen kaldırır. Şunları ~/.bashrc dosyanıza ekleyin:

    alias dc='docker compose'
    alias art='docker compose exec app php artisan'
    alias comp='docker compose exec app composer'
    alias dsh='docker compose exec app bash'
    

    Bundan sonra günlük iş akışınız şuna benzer:

    art make:model Siparis -mcr      # model, migration, controller, resource
    art migrate:fresh --seed          # veritabanını sıfırla
    art tinker                        # etkileşimli kabuk
    comp require spatie/laravel-permission
    art queue:restart                 # kuyruk işçisine yeni kodu al dedirt
    dc logs -f queue                  # işçinin ne yaptığını izle
    

    Ön yüz varlıkları için Node'u da konteynerde tutabilirsiniz, ancak geliştirme sırasında sıcak yeniden yükleme (HMR) host makinede çalıştırmak genelde daha az sürtünme yaratır. Konteynerde çalıştıracaksanız Vite'ın dışarıdan erişilebilir olması için --host bayrağını vermeniz ve ilgili portu yayımlamanız gerekir. Node uygulamalarını konteynerleştirmenin ayrıntıları için Docker ile Node.js uygulaması yayınlama yazısına bakabilirsiniz.

    Kuyruk işçisiyle ilgili kritik bir davranışı unutmayın: işçi kodu bir kez belleğe alır ve çalışmaya devam eder. Kod değiştirdiğinizde işçi eski kodu çalıştırmaya devam eder. art queue:restart komutu işçilere mevcut işi bitirip kapanmalarını söyler, restart: unless-stopped politikası da onları yeniden başlatır. Bu ikili olmadan "değişikliğim neden işlemiyor" sorusuyla saatler geçirirsiniz.

    Sık Yapılan Hatalar#

    storage klasörüne yazamıyorThe stream or file could not be opened hatası izin sorunudur. Yukarıdaki UID yaklaşımını kullandıysanız bu genellikle hiç oluşmaz; yine de sorun yaşarsanız sahipliği düzeltin:

    sudo chown -R $(id -u):$(id -g) storage bootstrap/cache
    docker compose exec app php artisan optimize:clear
    

    SQLSTATE[HY000] [2002] Connection refused.env içinde DB_HOST=localhost kalmıştır. Konteyner ağında veritabanının adı mysql'dir. Değiştirdikten sonra yapılandırma önbelleğini temizlemeyi unutmayın: art config:clear.

    Değişiklikler yansımıyor — Laravel yapılandırma, rota ve görünüm önbelleklerini tutar. Geliştirmede art optimize:clear komutu hepsini birden temizler. Ayrıca üretim ayarlarıyla derlenmiş bir imaj kullanıyorsanız OPcache zaman damgası doğrulaması kapalı olabilir; geliştirme için açık olmalıdır.

    Windows ve WSL'de aşırı yavaşlık — Proje dosyaları Windows dosya sisteminde durup konteynere bağlanıyorsa her dosya okuma bir çeviri katmanından geçer ve Laravel binlerce dosya okur. Çözüm, projeyi WSL'in kendi dosya sisteminde (/home/kullanici/...) tutmaktır; fark on kata varan hızlanma olabilir.

    Kuyruk işi tekrar tekrar deneniyor — İş başarısız oluyor ama sebebini görmüyorsanız failed_jobs tablosuna bakın: art queue:failed. Sonsuz denemeyi önlemek için --tries değerini mutlaka verin; sınırsız deneme, hatalı bir iş yüzünden kuyruğun tıkanmasına yol açar.

    Konteyner sürekli yeniden başlıyor — Genellikle queue servisinde .env eksik olduğu için artisan çöker ve restart politikası döngüye sokar. Logdan gerçek hatayı okuyun; bu döngünün genel nedenlerini konteyner sürekli yeniden başlıyor yazısında ele aldık.

    Sıkça Sorulan Sorular#

    Docker Laravel geliştirmesini yavaşlatır mı#

    Linux üzerinde hissedilir bir fark olmaz; dosya sistemi doğrudan paylaşılır. macOS ve Windows'ta bağlı dizinlerin okuma performansı daha düşüktür ve Laravel çok sayıda küçük dosya okuduğu için bu fark sayfa yüklenmesinde görülebilir. WSL kullanıcıları projeyi Linux dosya sisteminde tutarak, macOS kullanıcıları ise dosya senkronizasyon ayarlarını düzenleyerek bunu büyük ölçüde giderebilir.

    Laravel Sail ile bu kurulum arasındaki fark nedir#

    Sail, Laravel ekibinin hazırladığı hazır bir Docker yapılandırmasıdır ve hızlı başlamak için idealdir. Buradaki yaklaşım ise imajı ve servisleri kendiniz tanımladığınız için tam kontrol sağlar: PHP eklentilerini seçersiniz, gereksiz servisleri hiç açmazsınız ve aynı Dockerfile mantığını üretime taşıyabilirsiniz. Sail ile başlayıp ihtiyaç büyüdükçe kendi yapılandırmanıza geçmek de makul bir yoldur.

    Bu ortamı doğrudan üretimde kullanabilir miyim#

    Doğrudan değil. Geliştirme ortamında kod bir hacimle bağlanır; üretimde kod imajın içine kopyalanmalıdır ki dağıtım değişmez (immutable) olsun. Ayrıca üretimde APP_DEBUG=false olmalı, art config:cache ve art route:cache çalıştırılmalı, OPcache zaman damgası doğrulaması kapatılmalı ve konteynerlere kaynak limiti konmalıdır. Yapı aynı kalır, ayrıntılar değişir.

    Veritabanı verimi nasıl yedeklerim#

    Veri adlandırılmış bir hacimde durur, bu yüzden klasör kopyalamak yerine mantıksal döküm almak en güvenli yoldur. docker compose exec mysql mysqldump -u root -p laravel > yedek.sql komutu tek dosyalık bir yedek üretir. Geri yüklerken aynı komutu ters yönde kullanırsınız. Konteyner verilerini topluca yedeklemenin yöntemlerini Docker uygulama yedekleme ve geri yükleme yazısında bulabilirsiniz.

    Birden fazla Laravel projesini aynı anda çalıştırabilir miyim#

    Evet, ancak yayımlanan portlar çakışmamalıdır. Her proje için farklı bir host portu seçin (8080, 8081, 8082) ya da hepsinin önüne bir ters proxy koyup alan adına göre yönlendirin. İkinci yöntem daha temizdir; her projeye proje1.test gibi bir yerel alan adı verirsiniz ve port numarası ezberlemekten kurtulursunuz.

    E-postaları geliştirmede nasıl test ederim#

    Gerçek bir SMTP sunucusuna bağlanmak yerine, gelen postaları yakalayıp bir web arayüzünde gösteren bir servis konteyneri ekleyin ve .env içindeki MAIL_HOST değerini o servise yönlendirin. Böylece test verileriyle gerçek kişilere posta gitmesi riski ortadan kalkar. Üretimde gerçek gönderim yapacaksanız SMTP ayarlarınızı SMTP test aracı ile doğrulayabilirsiniz.

    Kapanış#

    Laravel'i Docker'da rahat kullanmanın sırrı birkaç alışkanlıkta toplanıyor: imajı host kullanıcınızla aynı UID'ye sahip bir kullanıcıyla derlemek, composer ve artisan komutlarını daima konteyner içinde çalıştırmak, .env içindeki ana makine adlarını servis adlarıyla değiştirmek, kuyruk işçisini ayrı bir servis olarak tanımlayıp kod değişince queue:restart demek ve veritabanını adlandırılmış hacimde tutup düzenli döküm almak. Bu beşini oturttuğunuzda ortam kurulumu bir daha gündeminize gelmez.

    Projeyi yayına alma vakti geldiğinde tam root erişimli bir ortam gerekir; VDS ve bulut sunucu paketlerimiz Docker tabanlı dağıtım için uygundur. Sunucu bakımını, güncellemeleri ve izlemeyi devretmek isterseniz sunucu yönetimi hizmetimiz bunu üstlenir; mevcut projenizi başka bir sağlayıcıdan taşıyacaksanız site taşıma hizmetimiz geçişi planlı biçimde yürütür.

    LaravelDockerPHP

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.