Symfony dan Docker di 2026: Lingkungan Pengembangan dan Deployment Produksi
Alur kerja Symfony Docker lengkap untuk 2026: lingkungan pengembangan FrankenPHP, image produksi multi-stage, mode worker, secret terenkripsi, optimasi image, dan deploy tanpa downtime dengan migrasi.

Tutorial Symfony Docker ini membangun alur kerja kontainer yang lengkap: sebuah lingkungan pengembangan lokal yang dapat direproduksi dan sebuah image produksi yang diperkuat, keduanya berpusat pada FrankenPHP dan Symfony 7.4 LTS. Menjalankan Symfony di dalam kontainer menghilangkan celah environment-drift klasik antara laptop dan server, sekaligus mengubah setiap deployment menjadi pengiriman satu artefak yang tidak dapat diubah, bukan menjalankan rangkaian perintah server manual yang rapuh.
Template resmi Symfony Docker kini menyertakan FrankenPHP sebagai runtime-nya, menggantikan pasangan tradisional Nginx dan PHP-FPM. Satu binary melayani HTTP/2 dan HTTP/3, serta menjalankan Symfony dalam mode worker persisten dengan mempertahankan kernel tetap ter-boot di antara request alih-alih membangunnya ulang pada setiap panggilan.
Lingkungan Pengembangan Docker Compose untuk Symfony
Setup pengembangan yang baik memberikan setiap kontributor versi PHP yang sama, ekstensi yang sama, dan database yang sama tanpa menyentuh mesin host. Docker Compose mendeskripsikan stack tersebut secara deklaratif. Contoh di bawah memadukan kontainer FrankenPHP yang dibangun dari Dockerfile lokal dengan layanan PostgreSQL 17, yang saling terhubung pada jaringan Compose bawaan.
# 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:Bind mount pada layanan php memetakan root proyek ke dalam kontainer, sehingga perubahan file berlaku tanpa perlu membangun ulang image. FrankenPHP menghasilkan sertifikat TLS lokal saat boot pertama, itulah alasan port 443 diekspos dan caddy_data dijadikan named volume: sertifikat tersebut tetap bertahan setelah docker compose down. Petunjuk serverVersion=17 pada DSN membuat Doctrine dapat melewati query deteksi versi pada setiap koneksi.
Setelah stack berjalan, setiap perintah Symfony dieksekusi di dalam kontainer php, bukan di host, sehingga versi dan ekstensi PHP yang benar terjamin. Membuat database, menjalankan migrasi, atau membuka shell semuanya mengikuti pola yang sama melalui docker compose exec.
Awali panggilan Symfony dan Composer dengan docker compose exec php agar dieksekusi terhadap runtime PHP kontainer, misalnya docker compose exec php bin/console make:entity atau docker compose exec php composer require symfony/uid. Host tidak pernah perlu memasang PHP.
Dockerfile Multi-Stage untuk Produksi Symfony
Sebuah Dockerfile multi-stage berbagi base yang sama lalu memisah menjadi target pengembangan dan target produksi. Stage produksi hanya memasang dependensi runtime, membuang paket dev Composer, dan menghangatkan cache saat build sehingga kontainer yang berjalan langsung siap seketika.
# 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 /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:warmupMenyalin composer.json dan file lock sebelum sisa kode sumber adalah optimasi kuncinya: Docker meng-cache layer instalasi dependensi dan hanya menjalankannya ulang ketika manifest berubah, bukan pada setiap perubahan kode. Flag --classmap-authoritative memberi tahu Composer bahwa classmap sudah lengkap, sehingga autoloader tidak pernah jatuh ke pencarian filesystem saat runtime. Menghangatkan cache selama build berarti request produksi pertama akan mengenai kontainer yang telah terkompilasi penuh.
Mode Worker FrankenPHP dan Symfony Runtime
PHP-FPM tradisional mem-boot kernel Symfony, menangani satu request, lalu membuang semuanya. Mode worker mempertahankan kernel dan container dependency-injection tetap hidup di antara request, dan di situlah sebagian besar penghematan latensi berasal. Paket runtime/frankenphp-symfony membuat mekanisme ini bekerja melalui komponen Symfony Runtime dan bootstrap public/index.php standar.
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']);
};Baris FRANKENPHP_CONFIG="worker ./public/index.php" yang diatur di dalam Dockerfile mengaktifkan loop ini. Karena kontainer digunakan kembali, layanan apa pun yang menyimpan state khusus request harus mereset dirinya di antara request. Symfony menangani layanan framework secara otomatis, tetapi layanan stateful buatan sendiri sebaiknya mengimplementasikan Symfony\Contracts\Service\ResetInterface dan diberi tag kernel.reset agar state yang terakumulasi tidak bocor ke request berikutnya. Ini adalah disiplin yang sama seperti yang dituntut oleh Messenger worker yang berjalan lama, dan imbalannya adalah throughput request beberapa kali lebih tinggi daripada PHP-FPM pada perangkat keras yang sama.
Untuk pengembangan lokal, menjalankan FrankenPHP dengan flag --watch akan me-restart worker secara otomatis ketika file berubah, sehingga mode worker tidak mengganggu loop edit-refresh.
FrankenPHP Versus PHP-FPM pada Symfony
Pilihan antara FrankenPHP dan stack klasik Nginx plus PHP-FPM membentuk baik Dockerfile maupun perilaku runtime. Tabel di bawah merangkum perbedaan praktisnya untuk setup produksi Symfony.
| Aspek | Mode worker FrankenPHP | Nginx + PHP-FPM | |--------|------------------------|-----------------| | Kontainer | Satu | Dua (web server + PHP) | | Boot kernel | Sekali per worker | Sekali per request | | HTTP/3 | Bawaan | Perlu konfigurasi tambahan | | TLS | Otomatis (Caddy) | Setup sertifikat manual | | Penanganan state | Harus reset di antara request | Terisolasi per request | | Biaya cold-start | Dibayar sekali saat boot | Dibayar pada setiap request |
Mode worker unggul dalam throughput karena mengamortisasi langkah boot kernel dan kompilasi container yang mahal ke seluruh ribuan request. PHP-FPM tetap relevan ketika sebuah aplikasi bergantung pada global atau library pihak ketiga yang mengasumsikan proses baru per request, karena hal-hal tersebut akan rusak di bawah worker yang digunakan kembali.
Sebuah layanan yang meng-cache data request di dalam properti privat akan menyajikan data satu request ke request berikutnya kecuali ia mereset diri. Audit singleton, event subscriber, dan apa pun yang menyimpan token keamanan atau request saat ini sebelum beralih ke mode worker di produksi.
Siap menguasai wawancara Symfony Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Mengelola Environment Variable dan Secret di Produksi
Symfony membaca konfigurasi dari environment variable, dan ada dua cara yang solid untuk menyuplainya di produksi. Cara pertama adalah menyuntikkan variabel biasa melalui orchestrator atau file Compose. Cara kedua adalah vault secret terenkripsi milik Symfony, yang menyimpan nilai sensitif di dalam repository sebagai ciphertext dan mendekripsinya saat runtime dengan satu kunci privat.
# 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)"Langkah composer dump-env prod di dalam Dockerfile mengkompilasi file .env menjadi satu .env.local.php yang teroptimasi, yang menghilangkan biaya runtime untuk mem-parsing file dotenv saat boot. Variabel apa pun yang diatur di environment kontainer sebenarnya tetap menimpa nilai default terkompilasi, sehingga secret yang disediakan orchestrator selalu menang. Panduan deployment Symfony mendokumentasikan urutan presedensi lengkapnya, tetapi aturan praktisnya sederhana: jangan pernah memanggang APP_SECRET atau kredensial database ke dalam image, dan suntikkan keduanya saat kontainer dimulai.
Mengoptimalkan Image Produksi Symfony
Dua pengaturan OPcache menyumbang sebagian besar kesenjangan performa produksi: menonaktifkan validasi timestamp dan mengaktifkan preloading. Karena image kontainer bersifat tidak dapat diubah, kode sumber tidak pernah berubah saat runtime, sehingga OPcache tidak seharusnya pernah men-stat file untuk memeriksa perubahan. Preloading melangkah lebih jauh dengan memuat kelas Symfony dan aplikasi ke dalam shared memory sekali saat server dimulai.
; 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=600Symfony menghasilkan file preload.php yang tercantum di atas selama cache:warmup, sehingga file tersebut sudah ada di dalam image setelah build. Preloading OPcache menautkan kelas-kelas ini saat startup dan melewati langkah kompilasi pada request pertama yang membutuhkannya. Menetapkan validate_timestamps=0 hanya aman untuk deployment yang tidak dapat diubah di mana rilis baru berarti image baru; pada host yang dapat diubah hal ini akan menyajikan kode usang. Menetapkan max_accelerated_files di atas jumlah file sebenarnya menjaga seluruh framework tetap ter-cache tanpa eviction.
Mengurangi Ukuran Image Symfony dengan .dockerignore
File .dockerignore yang hilang secara diam-diam menggembungkan build. Tanpanya, langkah COPY . . akan mengirim direktori vendor lokal, var/cache dari pengembangan, output build node, dan riwayat .git langsung ke dalam image dan konteks build. Mengecualikan semuanya akan mengecilkan image, mempercepat unggahan konteks build ke daemon, dan mencegah artefak pengembangan menimpa dependensi produksi yang baru saja terpasang.
# .dockerignore
/.git/
/vendor/
/node_modules/
/var/
/.env.local
/.env.*.local
/tests/
/docker/
compose*.yaml
DockerfileMengabaikan /vendor/ adalah yang paling penting: stage produksi menjalankan composer install --no-dev, sehingga mengirim direktori vendor host yang bercita rasa pengembangan akan menggagalkan langkah tersebut sepenuhnya. Mengecualikan /var/ menjaga cache dan log pengembangan tetap keluar dari image, membiarkan cache:warmup saat build menghasilkan cache produksi yang bersih. Dikombinasikan dengan build multi-stage dan ekstensi berbasis Alpine, image Symfony yang ramping biasanya berukuran jauh di bawah 150 MB.
Men-deploy Kontainer Symfony dan Menjalankan Migrasi
File Compose produksi mereferensikan image yang telah dibangun sebelumnya dari registry alih-alih membangunnya secara lokal, dan ia mendefinisikan health check agar orchestrator hanya mengarahkan trafik ke kontainer yang merespons. Secret tiba sebagai environment variable, tidak pernah sebagai layer image.
# 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-stoppedHealth check tersebut men-curl route /health, jadi aplikasi harus mengeksposnya. Sebuah controller minimal yang mengembalikan status 200 tanpa menyentuh database menjaga check tetap cepat dan menghindari penandaan kontainer sebagai tidak sehat selama gangguan database sesaat. Ketika kesiapan database penting, probe kedua yang lebih dalam dapat menjalankan query ringan, tetapi endpoint liveness tetap sederhana.
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']);
}
}Migrasi database sebaiknya dijalankan sebagai langkah terpisah sebelum kontainer baru menerima trafik, bukan di dalam boot aplikasi. Menjalankannya di dalam kontainer sekali pakai menjamin skema selalu terkini tepat satu kali per rilis, bahkan ketika beberapa replika aplikasi dimulai secara paralel.
# 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 --waitFlag --wait memblokir hingga setiap layanan melaporkan sehat, memberi skrip deploy sinyal keberhasilan yang nyata alih-alih start fire-and-forget. Memadukan ini dengan rolling update pada orchestrator seperti Kubernetes atau Docker Swarm menghasilkan rilis tanpa downtime: kontainer lama terus melayani hingga yang baru lolos health check-nya. Untuk gambaran lebih luas tentang versi framework yang menjadi target alur kerja ini, ikhtisar platform Symfony dan panduan fitur Symfony 8 dan PHP 8.4 membahas apa yang berubah pada lini rilis saat ini.
Kesimpulan
- Gunakan Dockerfile multi-stage agar image pengembangan dan produksi berbagi satu base sementara target produksi dikirim tanpa dependensi dev Composer
- Salin
composer.jsondan file lock sebelum kode sumber untuk menjaga layer instalasi dependensi tetap ter-cache lintas perubahan kode - Jalankan FrankenPHP dalam mode worker untuk menggunakan kembali kernel yang telah ter-boot lintas request, dan reset layanan stateful dengan
ResetInterfaceuntuk mencegah kebocoran state - Hangatkan cache Symfony dan kompilasi
.envdengandump-env prodsaat build sehingga kontainer yang berjalan langsung terinisialisasi penuh - Aktifkan preloading OPcache dan tetapkan
validate_timestamps=0di produksi, yang aman justru karena image bersifat tidak dapat diubah - Jauhkan
APP_SECRET, kredensial database, dan kunci dekripsi vault dari layer image; suntikkan semuanya sebagai environment variable saat kontainer dimulai - Terapkan migrasi Doctrine di dalam kontainer sekali pakai sebelum replika baru menerima trafik, dan gerbangi rollout pada health check dengan
up -d --wait
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Tag
Bagikan
Artikel terkait

Pengujian Symfony di 2026: PHPUnit, KernelTestCase, dan Functional Tests
Panduan lengkap pengujian aplikasi Symfony menggunakan PHPUnit 12, KernelTestCase untuk integration testing, dan WebTestCase untuk functional tests dengan praktik terbaik.

Symfony Live Components dan UX 3.0: Aplikasi Reaktif Tanpa JavaScript di 2026
Symfony Live Components memungkinkan pengembangan antarmuka reaktif dengan PHP dan Twig tanpa JavaScript. Tutorial lengkap LiveProp, LiveAction, form, dan deferred loading.

Doctrine ORM: Menguasai Relasi di Symfony
Panduan lengkap relasi Doctrine ORM di Symfony. OneToMany, ManyToMany, strategi pemuatan, dan optimasi performa dengan contoh praktis.