# 2026'da Symfony ve Docker: Geliştirme Ortamı ve Üretim Dağıtımı > 2026 için eksiksiz bir Symfony Docker iş akışı: FrankenPHP geliştirme ortamı, çok aşamalı üretim imajı, worker modu, şifreli sırlar, imaj optimizasyonu ve migration'lı kesintisiz dağıtımlar. - Published: 2026-06-25 - Updated: 2026-07-06 - Author: SharpSkill - Tags: symfony, docker, frankenphp, php, deployment, devops - Reading time: 10 min --- Bu Symfony Docker rehberi eksiksiz bir konteyner iş akışı kurar: tekrarlanabilir bir yerel geliştirme ortamı ve sağlamlaştırılmış bir üretim imajı; her ikisi de FrankenPHP ve Symfony 7.4 LTS üzerine oturur. Symfony'yi konteynerlerde çalıştırmak, dizüstü bilgisayarlar ile sunucular arasındaki klasik ortam kayması sorununu ortadan kaldırır ve her dağıtımı, kırılgan bir dizi manuel sunucu komutu yerine tek ve değişmez bir yapıtın gönderilmesine dönüştürür. > **2026 Symfony Docker Yığını** > > Resmi Symfony Docker şablonu artık çalışma zamanı olarak [FrankenPHP](https://frankenphp.dev/) ile geliyor ve geleneksel Nginx artı PHP-FPM ikilisinin yerini alıyor. Tek bir ikili dosya HTTP/2 ve HTTP/3'ü sunar ve çekirdeği her çağrıda yeniden kurmak yerine istekler arasında açık tutarak Symfony'yi kalıcı worker modunda çalıştırır. ## Symfony için Docker Compose Geliştirme Ortamı İyi bir geliştirme kurulumu, her katkıda bulunana aynı PHP sürümünü, aynı eklentileri ve aynı veritabanını host makineye dokunmadan sağlar. Docker Compose bu yığını bildirimsel biçimde tanımlar. Aşağıdaki örnek, yerel bir `Dockerfile` üzerinden derlenen bir FrankenPHP konteynerini bir PostgreSQL 17 servisiyle eşleştirir ve ikisini varsayılan Compose ağı üzerinde birbirine bağlar. ```yaml # compose.yaml services: php: build: context: . target: frankenphp_dev ports: - "80:80" - "443:443" volumes: - ./:/app # bind mount: edits on the host appear instantly - caddy_data:/data # persist TLS certificates across restarts environment: DATABASE_URL: "postgresql://app:app@database:5432/app?serverVersion=17" depends_on: - database database: image: postgres:17-alpine environment: POSTGRES_DB: app POSTGRES_USER: app POSTGRES_PASSWORD: app volumes: - db_data:/var/lib/postgresql/data volumes: caddy_data: db_data: ``` `php` servisindeki bind mount, proje kök dizinini konteynerin içine eşler; böylece dosya değişiklikleri imajı yeniden derlemeden etkinleşir. FrankenPHP ilk açılışta yerel bir TLS sertifikası üretir; 443 portunun açılmasının ve `caddy_data`'nın adlandırılmış bir volume olmasının nedeni budur: sertifika `docker compose down` sonrasında da varlığını korur. DSN içindeki `serverVersion=17` ipucu, Doctrine'in her bağlantıda bir sürüm tespit sorgusu çalıştırmasını atlamasını sağlar. Yığın çalışmaya başladığında her Symfony komutu host üzerinde değil `php` konteyneri içinde yürütülür; bu da doğru PHP sürümünü ve eklentilerini garanti eder. Veritabanı oluşturmak, migration'ları çalıştırmak veya bir kabuk açmak, `docker compose exec` üzerinden aynı deseni izler. > **Konsol Komutlarını Konteyner İçinde Çalıştırmak** > > Symfony ve Composer çağrılarını, konteynerin PHP çalışma zamanına karşı çalıştırmak için `docker compose exec php` ile öne ekleyin; örneğin `docker compose exec php bin/console make:entity` veya `docker compose exec php composer require symfony/uid`. Host'ta hiçbir zaman PHP kurulu olması gerekmez. ## Symfony Üretimi için Çok Aşamalı Dockerfile Bir [çok aşamalı Dockerfile](https://docs.docker.com/build/building/multi-stage/) ortak bir temeli paylaşır ve ardından bir geliştirme hedefi ile bir üretim hedefine ayrılır. Üretim aşaması yalnızca çalışma zamanı bağımlılıklarını kurar, Composer'ın geliştirme paketlerini dışarıda bırakır ve önbelleği derleme sırasında ısıtarak çalışan konteynerin anında başlamasını sağlar. ```dockerfile # Dockerfile FROM dunglas/frankenphp:1-php8.4 AS base WORKDIR /app # Install the PHP extensions Symfony relies on RUN install-php-extensions \ intl \ opcache \ pdo_pgsql \ zip COPY --from=composer/composer:2-bin /composer /usr/bin/composer # --- Development target --- FROM base AS frankenphp_dev ENV APP_ENV=dev RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" # --- Production target --- FROM base AS frankenphp_prod ENV APP_ENV=prod ENV FRANKENPHP_CONFIG="worker ./public/index.php" ENV APP_RUNTIME="Runtime\FrankenPhpSymfony\Runtime" RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" # Layer caching: copy manifests first, install, then copy the source COPY composer.json composer.lock symfony.lock ./ RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist COPY . . RUN composer dump-autoload --no-dev --classmap-authoritative \ && composer dump-env prod \ && bin/console cache:warmup ``` `composer.json` ile kilit dosyalarını kaynağın geri kalanından önce kopyalamak temel optimizasyondur: Docker, bağımlılık kurulum katmanını önbelleğe alır ve bunu yalnızca manifest dosyaları değiştiğinde yeniden çalıştırır, her kod düzenlemesinde değil. `--classmap-authoritative` bayrağı Composer'a classmap'in eksiksiz olduğunu bildirir; böylece otomatik yükleyici çalışma zamanında dosya sistemi aramalarına asla geri düşmez. Önbelleği derleme sırasında ısıtmak, ilk üretim isteğinin tamamen derlenmiş bir konteynere ulaşması anlamına gelir. ## FrankenPHP Worker Modu ve Symfony Runtime Geleneksel PHP-FPM, Symfony çekirdeğini kurar, tek bir isteği işler ve her şeyi atar. Worker modu ise çekirdeği ve bağımlılık enjeksiyon konteynerini istekler arasında canlı tutar; gecikmedeki tasarrufların büyük bölümü buradan gelir. `runtime/frankenphp-symfony` paketi bunu Symfony Runtime bileşeni ve standart `public/index.php` başlangıç dosyası üzerinden mümkün kılar. ```php // public/index.php use App\Kernel; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (array $context) { return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); }; ``` Dockerfile'da tanımlanan `FRANKENPHP_CONFIG="worker ./public/index.php"` satırı bu döngüyü etkinleştirir. Konteyner yeniden kullanıldığı için isteğe özgü durum tutan her servisin kendini istekler arasında sıfırlaması gerekir. Symfony framework servislerini otomatik olarak yönetir, ancak durum tutan özel servislerin `Symfony\Contracts\Service\ResetInterface` arayüzünü uygulaması ve `kernel.reset` ile etiketlenmesi gerekir; böylece biriken durum bir sonraki isteğe sızmaz. Bu, uzun süre çalışan [Messenger worker'larının](/blog/symfony/symfony-messenger-queues-workers-async-architecture) gerektirdiği disiplinin aynısıdır ve karşılığında aynı donanımda PHP-FPM'e göre kat kat daha yüksek istek verimi elde edilir. Yerel geliştirmede FrankenPHP'yi `--watch` bayrağıyla çalıştırmak, dosyalar değiştiğinde worker'ı otomatik olarak yeniden başlatır; böylece worker modu düzenle-yenile döngüsünün önüne geçmez. ## Symfony'de FrankenPHP ile PHP-FPM Karşılaştırması FrankenPHP ile klasik Nginx artı PHP-FPM yığını arasındaki tercih, hem Dockerfile'ı hem de çalışma zamanı davranışını biçimlendirir. Aşağıdaki tablo, bir Symfony üretim kurulumu açısından pratik farkları özetler. | Boyut | FrankenPHP worker modu | Nginx + PHP-FPM | |--------|------------------------|-----------------| | Konteynerler | Bir | İki (web sunucusu + PHP) | | Çekirdek başlatma | Worker başına bir kez | İstek başına bir kez | | HTTP/3 | Yerleşik | Ek yapılandırma gerektirir | | TLS | Otomatik (Caddy) | Manuel sertifika kurulumu | | Durum yönetimi | İstekler arası sıfırlama şart | İstek başına yalıtılmış | | Soğuk başlatma maliyeti | Bir kez, açılışta ödenir | Her istekte ödenir | Worker modu, pahalı çekirdek ve konteyner derleme adımını binlerce isteğe yaydığı için verimde öne geçer. Global değişkenlere veya her işlem için taze bir süreç varsayan üçüncü taraf kütüphanelere dayanan uygulamalarda PHP-FPM hâlâ geçerliliğini korur; çünkü bunlar yeniden kullanılan bir worker altında bozulur. > **Worker Modunda Durum Sızıntıları** > > İstek verisini özel bir özellikte önbelleğe alan bir servis, kendini sıfırlamadıkça bir isteğin verisini bir sonrakine sunar. Üretimde worker moduna geçmeden önce singleton'ları, olay abonelerini ve güvenlik belirteci ya da mevcut isteği tutan her şeyi denetleyin. ## Üretimde Ortam Değişkenleri ve Sırların Yönetimi Symfony yapılandırmayı ortam değişkenlerinden okur ve üretimde bunları sağlamanın iki sağlam yolu vardır. İlki, orkestratör veya Compose dosyası aracılığıyla düz değişkenler enjekte etmektir. İkincisi, hassas değerleri depoda şifreli metin olarak saklayan ve çalışma zamanında tek bir özel anahtarla çözen Symfony'nin şifreli sır kasasıdır. ```bash # Generate the keypair; commit the public key, keep the private key out of the image php bin/console secrets:generate-keys # Encrypt a value into config/secrets/prod/ php bin/console secrets:set DATABASE_URL # In production, expose only the decryption key to the container export SYMFONY_DECRYPTION_SECRET="$(cat config/secrets/prod/prod.decrypt.private.php)" ``` Dockerfile'daki `composer dump-env prod` adımı `.env` dosyalarını tek ve optimize edilmiş bir `.env.local.php` dosyasında derler; bu da dotenv dosyalarını açılışta ayrıştırmanın çalışma zamanı maliyetini ortadan kaldırır. Gerçek konteyner ortamında tanımlanan herhangi bir değişken yine de derlenmiş varsayılanları geçersiz kılar; dolayısıyla orkestratör tarafından sağlanan sırlar her zaman üstün gelir. [Symfony dağıtım kılavuzu](https://symfony.com/doc/current/deployment.html) tam öncelik sırasını belgeler, ancak pratik kural basittir: `APP_SECRET` veya veritabanı kimlik bilgilerini asla imaja gömmeyin, bunları konteyner başlangıcında enjekte edin. ## Symfony Üretim İmajını Optimize Etmek İki OPcache ayarı, üretimdeki performans farkının büyük bölümünü açıklar: zaman damgası doğrulamasını kapatmak ve ön yüklemeyi (preloading) etkinleştirmek. Bir konteyner imajı değişmez olduğundan kaynak çalışma zamanında hiç değişmez; bu nedenle OPcache düzenleme olup olmadığını kontrol etmek için dosyaları asla stat etmemelidir. Ön yükleme bir adım daha ileri gider ve Symfony ile uygulama sınıflarını sunucu başlangıcında bir kez paylaşımlı belleğe yükler. ```ini ; docker/php/prod.ini opcache.enable=1 opcache.preload=/app/var/cache/prod/App_KernelProdContainer.preload.php opcache.preload_user=root opcache.memory_consumption=256 opcache.max_accelerated_files=20000 opcache.validate_timestamps=0 ; immutable image: never re-check the filesystem realpath_cache_size=4096K realpath_cache_ttl=600 ``` Symfony yukarıda listelenen `preload.php` dosyasını `cache:warmup` sırasında üretir; dolayısıyla dosya, derlemeden sonra imajda zaten mevcuttur. [OPcache ön yükleme](https://www.php.net/manual/en/opcache.preloading.php) bu sınıfları başlangıçta bağlar ve bunlara ihtiyaç duyan ilk istekte derleme adımını atlar. `validate_timestamps=0` ayarı yalnızca yeni bir sürümün yeni bir imaj anlamına geldiği değişmez dağıtımlarda güvenlidir; değiştirilebilir bir host'ta eski kodu sunardı. `max_accelerated_files` değerini gerçek dosya sayısının üzerinde tutmak, tüm framework'ün tahliye olmadan önbellekte kalmasını sağlar. ## .dockerignore ile Symfony İmaj Boyutunu Küçültmek Eksik bir `.dockerignore` dosyası derlemeyi sessizce şişirir. Onsuz, `COPY . .` adımı yerel `vendor` dizinini, geliştirmedeki `var/cache`'i, node derleme çıktısını ve `.git` geçmişini doğrudan imaja ve derleme bağlamına gönderir. Bunları hariç tutmak imajı küçültür, derleme bağlamının daemon'a yüklenmesini hızlandırır ve geliştirme yapıtlarının yeni kurulmuş üretim bağımlılıklarının üzerine yazmasını engeller. ```gitignore # .dockerignore /.git/ /vendor/ /node_modules/ /var/ /.env.local /.env.*.local /tests/ /docker/ compose*.yaml Dockerfile ``` `/vendor/`'ı yok saymak en önemlisidir: üretim aşaması `composer install --no-dev` çalıştırır, dolayısıyla host'un geliştirme kokan vendor dizinini göndermek bu adımı tamamen boşa çıkarır. `/var/`'ı hariç tutmak geliştirme önbelleğini ve günlüklerini imajın dışında tutar; böylece derleme zamanındaki `cache:warmup` temiz bir üretim önbelleği üretir. Çok aşamalı derleme ve Alpine tabanlı eklentilerle birleştiğinde, yalın bir Symfony imajı genellikle 150 MB'nin oldukça altına iner. ## Symfony Konteynerlerini Dağıtmak ve Migration Çalıştırmak Üretim Compose dosyası, yerel olarak derlemek yerine bir kayıt defterinden (registry) önceden derlenmiş bir imaja başvurur ve orkestratörün yalnızca yanıt veren bir konteynere trafik yönlendirmesi için bir sağlık kontrolü tanımlar. Sırlar imaj katmanları olarak değil, ortam değişkenleri olarak ulaşır. ```yaml # compose.prod.yaml services: php: image: registry.example.com/app:${TAG} environment: APP_ENV: prod APP_SECRET: ${APP_SECRET} DATABASE_URL: ${DATABASE_URL} healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost/health"] interval: 10s timeout: 3s retries: 3 restart: unless-stopped ``` Sağlık kontrolü bir `/health` rotasını curl'ler, dolayısıyla uygulamanın böyle bir rota sunması gerekir. Veritabanına dokunmadan 200 döndüren minimal bir controller, kontrolü hızlı tutar ve geçici bir veritabanı kesintisi sırasında konteynerin sağlıksız işaretlenmesini önler. Veritabanı hazırlığı önemli olduğunda, hafif bir sorgu çalıştıran ikinci ve daha derin bir prob eklenebilir, ancak canlılık uç noktası basit kalır. ```php // src/Controller/HealthController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; final class HealthController extends AbstractController { #[Route('/health', name: 'health', methods: ['GET'])] public function __invoke(): JsonResponse { return new JsonResponse(['status' => 'ok']); } } ``` Veritabanı migration'ları, uygulama açılışının içinde değil, yeni konteynerler trafik almadan önce ayrı bir adım olarak çalıştırılmalıdır. Bunları tek seferlik bir konteynerde çalıştırmak, birden fazla uygulama kopyası paralel olarak başlasa bile şemanın her sürümde tam olarak bir kez güncellenmesini garanti eder. ```bash # deploy.sh set -euo pipefail docker compose -f compose.prod.yaml pull # Apply migrations once, in an isolated container docker compose -f compose.prod.yaml run --rm php \ bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration # Start replicas and wait for the health check to pass docker compose -f compose.prod.yaml up -d --wait ``` `--wait` bayrağı her servis sağlıklı olduğunu bildirene kadar bloklar; böylece dağıtım betiğine bırak-ve-unut bir başlangıç yerine gerçek bir başarı sinyali verir. Bunu Kubernetes veya Docker Swarm gibi bir orkestratördeki kademeli güncellemeyle eşleştirmek, kesintisiz sürümler sağlar: eski konteynerler, yenileri sağlık kontrolünü geçene kadar hizmet vermeye devam eder. Bu iş akışının hedeflediği framework sürümlerine daha geniş bir bakış için [Symfony platform genel bakışı](/technologies/symfony) ve [Symfony 8 ve PHP 8.4 özellik kılavuzu](/blog/symfony/symfony-8-new-features-php-84-lazy-objects) güncel sürüm hattında nelerin değiştiğini ele alır. ## Sonuç - Geliştirme ve üretim imajlarının tek bir temeli paylaşması, üretim hedefinin ise Composer geliştirme bağımlılıkları olmadan gönderilmesi için çok aşamalı bir Dockerfile kullanın - Bağımlılık kurulum katmanını kod değişiklikleri boyunca önbellekte tutmak için `composer.json` ve kilit dosyalarını kaynaktan önce kopyalayın - Kurulan çekirdeği istekler arasında yeniden kullanmak için FrankenPHP'yi worker modunda çalıştırın ve durum sızıntılarını önlemek için durum tutan servisleri `ResetInterface` ile sıfırlayın - Çalışan konteynerin tamamen başlatılmış olarak başlaması için Symfony önbelleğini ısıtın ve `.env`'i derleme zamanında `dump-env prod` ile derleyin - Üretimde OPcache ön yüklemeyi etkinleştirin ve `validate_timestamps=0` ayarlayın; bu, tam olarak imaj değişmez olduğu için güvenlidir - `APP_SECRET`'i, veritabanı kimlik bilgilerini ve kasa çözme anahtarını imaj katmanlarından uzak tutun; bunları konteyner başlangıcında ortam değişkenleri olarak enjekte edin - Doctrine migration'larını, yeni kopyalar trafik almadan önce tek seferlik bir konteynerde uygulayın ve dağıtımı `up -d --wait` ile sağlık kontrolüne bağlayın --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/symfony/symfony-docker-development-production