Containerizing Legacy PHP Applications with Docker Multi-Stage Builds
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap pengembangan web modern, aplikasi PHP monolitik warisan (legacy monolithic applications) masih mendominasi sebagian besar infrastruktur enterprise, mulai dari sistem e-commerce berbasis Magento hingga platform kustom yang dibangun di atas framework kuno. Tantangan terbesar bagi seorang Web Architect dan Infrastructure Engineer bukanlah menulis kode baru, melainkan bagaimana membungkus (containerize) ekosistem monolitik ini ke dalam Docker tanpa melahirkan image raksasa yang berukuran gigabytes.
Secara historis, pembangunan image Docker untuk PHP monolith sering kali dilakukan menggunakan pendekatan Single-Stage Build. Pendekatan naif ini mencampurkan seluruh alat pengembangan (development tools, build dependencies, Composer cache, Git history, header files) langsung ke dalam image akhir produksi. Hasilnya adalah image berukuran 1.2GB hingga 2.5GB yang memicu berbagai masalah operasional serius:
- Bottleneck Jaringan (Registry Pull Latency): Saat melakukan proses rolling update pada orkestrator seperti Kubernetes atau Docker Swarm, node baru harus mengunduh image berukuran gigabyte dari container registry, memperlambat waktu pemulihan (recovery time) saat terjadi kegagalan infrastruktur.
- Risiko Keamanan Tinggi (Attack Surface): Menyertakan compiler, manajer paket, dan pustaka pengembangan di dalam kontainer produksi membuka celah keamanan yang sangat lebar jika penyerang berhasil mendapatkan akses eksekusi jarak jauh (RCE).
- Pemborosan Biaya Penyimpanan: Biaya penyimpanan pada Elastic Container Registry (ECR), Google Artifact Registry (GAR), atau Docker Hub melonjak drastis akibat ukuran image yang tidak efisien.
Kondisi: Sebelum vs Sesudah Penerapan Multi-Stage Builds
| Parameter Perbandingan | Sebelum (Single-Stage Build) | Sesudah (Docker Multi-Stage Optimization) |
|---|---|---|
| Ukuran Image Akhir | 1.4 GB – 2.8 GB | 120 MB – 250 MB |
| Waktu Build | Lambat (Setiap cache ulang) | Cepat (Memanfaatkan Layer Caching) |
| Jumlah Package OS | Puluhan package dev/build | Hanya runtime dependencies esensial |
| Attack Surface | Luas (Compiler & Dev tools) | Minimal (Clean production environment) |
| Deployment Velocity | Lambat (Pull time tinggi) | Cepat (Ringan & Efisien) |
[!WARNING] Jangan pernah menggunakan image
php:latestatau tag tanpa versi spesifik dalam lingkungan produksi. Selalu tentukan versi minor dan major spesifik, sepertiphp:8.2-fpm-alpine, untuk memastikan stabilitas dan prediktabilitas sistem.
2. Konsep Inti & Deep Dive Arsitektur
Docker Multi-Stage Builds adalah fitur revolusioner yang diperkenalkan sejak Docker 17.05. Fitur ini memungkinkan pengembang untuk menggunakan beberapa instruksi FROM di dalam satu file Dockerfile. Setiap instruksi FROM memulai babak baru pembangunan (stage) dengan base image yang berbeda, dan yang paling penting, Anda dapat menyalin artefak dari satu tahap ke tahap lainnya tanpa membawa beban dari toolchain pembangunan tahap sebelumnya.
Dalam arsitektur aplikasi PHP, proses pembangunan aplikasi umumnya terbagi menjadi dua fase utama:
- Build Stage (Composer Dependency Resolution): Fase di mana kita membutuhkan PHP runtime lengkap, ekstensi sistem operasi tambahan (
git,unzip,libpng-dev, dll), serta binarycomposeruntuk mengunduh dan mengkompilasi vendor dependencies. - Production Runtime Stage: Fase di mana kita hanya membutuhkan PHP-FPM yang bersih, file kode sumber aplikasi, serta vendor folder hasil kompilasi dari tahap pertama, tanpa menyertakan compiler dan file sampah.
Berikut adalah diagram ASCII yang mengilustrasikan alur eksekusi data dan transfer artefak di dalam Docker Multi-Stage Build:
+-------------------------------------------------------------+
| STAGE 1: BUILD & VENDOR |
| Base: php:8.2-cli |
| - Install system dependencies (git, unzip, libpq-dev) |
| - Copy composer.json & composer.lock |
| - Run: composer install --no-dev --optimize-autoloader |
+-------------------------------------------------------------+
|
| (Copy /app/vendor via COPY --from=0)
v
+-------------------------------------------------------------+
| STAGE 2: PRODUCTION RUNTIME |
| Base: php:8.2-fpm-alpine |
| - Install minimal runtime extensions (pdo_mysql, redis) |
| - Copy application source code |
| - Copy compiled vendor/ from Stage 1 |
| - Apply proper file permissions (www-data) |
+-------------------------------------------------------------+
|
v
[Final Clean Production Image]
3. Perbandingan Komparatif / Analisis Pendekatan
Dalam membangun container untuk PHP, pemilihan base image sangat menentukan performa dan ukuran akhir. Berikut adalah analisis mendalam antara tiga base image populer yang sering digunakan:
1. Alpine Linux (php:8.2-fpm-alpine)
- Kelebihan: Ukuran sangat kecil (di bawah 50MB untuk base), menggunakan musl libc, sangat cepat diunduh.
- Kekurangan: Beberapa ekstensi PHP native atau modul C eksternal memerlukan kompilasi manual karena perbedaan pustaka C (
muslvsglibc). Kompatibilitas dengan pustaka enterprise tertentu terkadang memicu segmentation fault yang sulit didebug. - Sumber Daya: Penggunaan RAM sangat minimal (~15MB overhead idle).
2. Debian Slim (php:8.2-fpm-bookworm-slim)
- Kelebihan: Menggunakan glibc, standar industri yang paling kompatibel dengan hampir seluruh ekstensi PHP (
PECL), stabil, dan mudah dalam manajemen paketapt. - Kekurangan: Ukuran base image lebih besar dibandingkan Alpine (~80MB - 100MB).
- Sumber Daya: Penggunaan RAM moderat (~25MB overhead idle).
3. Ubuntu LTS (ubuntu/php:8.2-22.04_beta)
- Kelebihan: Sangat familiar bagi para sysadmin tradisional, dukungan repositori PPA yang melimpah.
- Kekurangan: Ukuran image paling gemuk di antara ketiganya (>150MB base), meningkatkan attack surface secara signifikan.
- Sumber Daya: Penggunaan RAM lebih tinggi.
[!TIP] Sebagai Web Architect, rekomendasi utama saya untuk produksi enterprise adalah menggunakan Debian Slim kecuali ada kebutuhan spesifik yang mewajibkan efisiensi ekstrem dari Alpine. Stabilitas glibc jauh lebih berharga daripada memangkas ukuran image sebesar 30MB.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum menyelam ke dalam implementasi teknis, pastikan environment lokal atau build server Anda telah memenuhi standar berikut:
- Docker Engine: Versi minimum
24.0.0atau lebih tinggi (mendukung BuildKit secara default). - Docker BuildKit: Pastikan BuildKit diaktifkan untuk mempercepat proses build melalui paralelisasi dan cache mount canggih. Aktifkan dengan perintah terminal:
export DOCKER_BUILDKIT=1 - Struktur Direktori Proyek:
/var/www/html/ ├── .dockerignore ├── Dockerfile ├── composer.json ├── composer.lock ├── public/ │ └── index.php ├── src/ └── nginx/ ├── conf.d/ │ └── default.conf └── nginx.conf
Buat file .dockerignore untuk mencegah pengiriman file sampah lokal ke dalam build context Docker:
.git
.env
node_modules
storage/*.key
var/
vendor/
Dockerfile
docker-compose*
README.md
.DS_Store
5. Panduan Implementasi Langkah demi Langkah
Berikut adalah implementasi nyata dari Dockerfile multi-stage yang dirancang khusus untuk aplikasi PHP monolitik enterprise, lengkap dengan konfigurasi Nginx dan PHP-FPM terintegrasi.
A. Dockerfile Multi-Stage Produksi
# ====================================================================
# STAGE 1: Build Vendor Dependencies
# ====================================================================
FROM composer:2.6 AS vendor_builder
WORKDIR /app
# Salin file definisi dependensi terlebih dahulu untuk mengoptimalkan layer caching
COPY composer.json composer.lock /
# Unduh seluruh dependensi production tanpa menyertakan dev packages
RUN composer install \
--no-dev \
--no-scripts \
--no-autoloader \
--prefer-dist \
--ansi
# Salin seluruh kode sumber aplikasi
COPY . /app
# Generate optimized autoloader
RUN composer dump-autoload --no-dev --optimize --classmap-authoritative
# ====================================================================
# STAGE 2: Production Runtime Environment
# ====================================================================
FROM php:8.2-fpm-bookworm-slim AS production
# Labeling metadata
LABEL maintainer="Infrastructure Engineering Team <infra@enterprise.com>"
# Set lingkungan non-interaktif
ENV DEBIAN_FRONTEND=noninteraktif \
PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \
PHP_OPCACHE_MAX_ACCELERATED_FILES=20000 \
PHP_OPCACHE_MEMORY_CONSUMPTION=256 \
PHP_OPCACHE_MAX_WASTED_PERCENTAGE=10
# Install ekstensi sistem operasi & pustaka esensial
RUN apt-get update && apt-get install -y --no-install-recommends \
nginx \
curl \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
libwebp-dev \
libzip-dev \
libicu-dev \
libpq-dev \
zlib1g-dev \
&& docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp \
&& docker-php-ext-install -j$(nproc) \
pdo \
pdo_mysql \
zip \
intl \
opcache \
exif \
bcmath \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Konfigurasi PHP Production Custom
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Salin file konfigurasi PHP tambahan
COPY ./docker/php/conf.d/custom.ini $PHP_INI_DIR/conf.d/custom.ini
# Buat direktori kerja aplikasi
WORKDIR /var/www/html
# Salin source code dari direktori lokal
COPY --chown=www-data:www-data . /var/www/html
# Salin vendor folder yang telah dikompilasi bersih dari STAGE 1
COPY --from=vendor_builder --chown=www-data:www-data /app/vendor /var/www/html/vendor
# Konfigurasi Nginx untuk menangani PHP-FPM
COPY ./docker/nginx/conf.d/default.conf /etc/nginx/sites-available/default
RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
# Pastikan direktori log dan run nginx memiliki izin akses yang tepat
RUN mkdir -p /var/log/nginx /var/run/nginx \
&& chown -R www-data:www-data /var/log/nginx /var/run/nginx /var/www/html
# Expose port HTTP
EXPOSE 80
# Salin skrip entrypoint untuk inisialisasi runtime
COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
USER www-data
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
B. Konfigurasi Nginx (docker/nginx/conf.d/default.conf)
server {
listen 80;
server_name localhost;
root /var/www/html/public;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
index index.php index.html index.htm;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Blok akses ke file tersembunyi
location ~ /. {
deny all;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
}
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
C. Skrip Entrypoint (docker/entrypoint.sh)
#!/usr/bin/env bash
set -e
echo "==> Menjalankan migrasi database atau pembersihan cache jika diperlukan..."
# Contoh: php artisan config:cache
echo "==> Memulai Nginx dan PHP-FPM..."
# Karena kita menjalankan Nginx dan PHP-FPM dalam satu kontainer, kita jalankan FPM di background lalu Nginx di foreground
php-fpm -D
exec nginx -g "daemon off;"
6. Bedah Kode & Penjelasan Detail Baris Kunci
COPY --from=vendor_builder ...: Inilah inti dari multi-stage builds. Perintah ini menyalin direktorivendoryang telah di-generate oleh containervendor_buildertanpa membawa sisa cache composer atau file konfigurasi development. Hasilnya, ukuran image terpangkas hingga 80%.--no-dev --optimize --classmap-authoritative: Kombinasi argumen Composer yang wajib digunakan di produksi. Perintah ini mengabaikan pustaka pengujian seperti PHPUnit, serta membangun classmap terpusat sehingga PHP tidak perlu melakukan filesystem lookup saat memuat kelas (autoloading optimization).OPCACHE_VALIDATE_TIMESTAMPS=0: Pengaturan kritis untuk produksi. Secara default, PHP memeriksa perubahan file setiap ada request HTTP. Menonaktifkan validasi timestamp memaksa OPcache melayani bytecode langsung dari memori RAM, mendongkrak performa hingga 300%.USER www-data: Menjalankan proses web server dan PHP-FPM di bawah non-root user untuk mencegah eskalasi hak istimewa jika terdapat celah keamanan pada aplikasi PHP.
7. Skenario Produksi & Studi Kasus Dunia Nyata
Dalam menghadapi traffic tinggi (misalnya flash sale e-commerce dengan 50,000 request per detik), menggabungkan Nginx dan PHP-FPM dalam satu kontainer (seperti contoh di atas) terkadang menemui kendala penskalaan horisontal yang independen.
Arsitektur enterprise tingkat lanjut menerapkan Separation of Concerns: memisahkan Nginx Reverse Proxy / Ingress Controller dengan Container PHP-FPM murni.
[ Cloudflare CDN / WAF ]
│
v
[ Nginx Ingress Controller (Load Balancer) ]
├──> [ PHP-FPM Pod 1 (Replica A) ]
├──> [ PHP-FPM Pod 2 (Replica B) ]
└──> [ PHP-FPM Pod 3 (Replica C) ]
Dengan memisahkan layanan Nginx dari kontainer aplikasi PHP-FPM, Anda dapat melakukan autoscaling pada kontainer PHP secara independen berdasarkan metrik CPU utilization atau antrean queue di Redis, tanpa terikat dengan beban koneksi statis Nginx.
8. Common Pitfalls, Anti-Patterns & Solusi
-
Anti-Pattern: Menyimpan file session dan upload di dalam direktori kontainer lokal.
- Masalah: Kontainer Docker bersifat ephemeral (bisa dihancurkan dan dibuat ulang kapan saja). Menyimpan file upload lokal akan menyebabkan data hilang saat kontainer melakukan restart atau rolling update.
- Solusi: Gunakan AWS S3, MinIO, atau shared Network File System (NFS) untuk penyimpanan file media, serta Redis untuk manajemen PHP session terpusat.
-
Pitfall: Menggunakan
COPY . /var/www/htmlsebelum menjalankancomposer install.- Masalah: Setiap kali Anda mengubah satu baris kode PHP pada file controller, Docker akan membuang cache layer composer install, memaksa sistem mengunduh ulang seluruh pustaka dari internet.
- Solusi: Selalu salin
composer.jsondancomposer.lockterlebih dahulu, jalankancomposer install, baru kemudian salin seluruh sisa kode sumber aplikasi.
-
Kesalahan Konfigurasi FPM Process Manager (
pm).- Masalah: Menggunakan pengaturan dynamic atau static yang tidak disesuaikan dengan kapasitas RAM kontainer memicu error
OOM Killer(Out of Memory). - Solusi: Hitung alokasi memori secara presisi. Jika kontainer dialokasikan 1GB RAM dan setiap proses PHP-FPM membutuhkan rata-rata 40MB, set nilai
pm.max_children = 20(menyisakan ruang untuk sistem operasi dan Nginx).
- Masalah: Menggunakan pengaturan dynamic atau static yang tidak disesuaikan dengan kapasitas RAM kontainer memicu error
9. Benchmark, Metrik Kinerja & Tuning Performa
Untuk memastikan image hasil containerisasi siap menghadapi beban produksi, lakukan pengujian beban (load testing) menggunakan tool industri seperti k6 atau wrk.
Contoh skrip pengujian menggunakan wrk dengan 12 thread dan 400 koneksi konkuren selama 30 detik:
wrk -t12 -c400 -d30s http://localhost/index.php
Parameter Tuning Lanjutan (www.conf PHP-FPM):
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
pm.max_requests: Mengatur jumlah request yang dilayani oleh satu proses pekerja sebelum proses tersebut didaur ulang (recycle). Nilai ini sangat penting untuk mencegah terjadinya memory leak laten yang sering menjangkiti aplikasi PHP monolitik warisan.
10. Checklist Penerapan Produksi & Kesimpulan
Sebelum meluncurkan image kontainer PHP Anda ke lingkungan produksi klaster Kubernetes atau Docker Swarm, pastikan checklist verifikasi akhir berikut telah terpenuhi selengkapnya:
- Ukuran final image berhasil ditekan di bawah 250MB menggunakan Multi-Stage Build.
- Tidak ada file
composer.json,git, atau compiler development yang terbawa di image produksi. - Proses berjalan di bawah non-root user (
www-data). - OPcache diaktifkan secara optimal dengan
opcache.validate_timestamps=0. - Seluruh rahasia aplikasi (
DB_PASSWORD,APP_KEY, dll) disuntikkan melalui Environment Variables atau Kubernetes Secrets, bukan di-hardcode di dalam image. - Healthcheck endpoint dikonfigurasi dengan benar untuk memantau status kesehatan kontainer.
Dengan menerapkan arsitektur Docker Multi-Stage Builds ini secara disiplin, Anda tidak hanya menyelamatkan infrastruktur dari pemborosan sumber daya dan celah keamanan, tetapi juga membangun fondasi deployment pipeline yang cepat, andal, dan siap diandalkan dalam skala enterprise.