Kenapa Docker Compose untuk PHP + Nginx
Menjalankan PHP dengan Nginx secara tradisional butuh instalasi terpisah: PHP-FPM, Nginx, ekstensi PHP, konfigurasi virtual host. Setiap pindah server atau onboarding developer baru harus mengulang setup yang sama.
Docker Compose menyatukan semua komponen dalam satu file YAML. Satu perintah docker compose up -d, semua service berjalan. Environment development dan production bisa identik.
Artikel ini membahas setup PHP-FPM + Nginx + MySQL menggunakan Docker Compose, siap untuk aplikasi Laravel, CodeIgniter, atau PHP native.
Arsitektur Container
Browser → Nginx (port 80) → PHP-FPM (port 9000, internal)
↓
MySQL (port 3306, internal)
Nginx menerima request HTTP dan meneruskannya ke PHP-FPM via FastCGI. MySQL hanya bisa diakses dari dalam Docker network — tidak terbuka ke internet.
Persiapan
mkdir -p ~/myapp/{src,nginx}
cd ~/myapp
1. Dockerfile PHP-FPM + Ekstensi
Buat Dockerfile:
FROM php:8.3-fpm
RUN apt-get update && apt-get install -y \
libpng-dev libjpeg-dev libfreetype6-dev \
libzip-dev unzip git curl \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd pdo pdo_mysql zip opcache
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
php:8.3-fpm— image PHP-FPM resmi- Ekstensi:
gd(gambar),pdo_mysql(database),zip(composer) composer:2— multi-stage build, ambil binary composer dari image terpisah
2. Konfigurasi Nginx
Buat nginx/default.conf:
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}
Penjelasan:
root /var/www/html/public— root folder Laravel (atau/var/www/htmluntuk PHP native)try_files ... /index.php?$query_string— rewrite rule untuk clean URLfastcgi_pass app:9000— kirim request PHP ke container bernamaapplocation ~ /\.— tolak akses ke file tersembunyi (.env, .git, dll.)
3. docker-compose.yml
services:
app:
build: .
container_name: php_app
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./src:/var/www/html
networks:
- app_net
depends_on:
- db
nginx:
image: nginx:alpine
container_name: nginx_web
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./src:/var/www/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- app_net
depends_on:
- app
db:
image: mysql:8.0
container_name: mysql_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: app_password
volumes:
- db_data:/var/lib/mysql
networks:
- app_net
ports:
- "127.0.0.1:3306:3306"
volumes:
db_data:
networks:
app_net:
driver: bridge
- app: build dari Dockerfile, volume source code
- nginx: image Alpine (ringan), port 80 terbuka ke host
- db: MySQL 8, volume persistent, port MySQL hanya bind ke localhost (127.0.0.1)
docker compose up -d --build
4. Optimasi untuk Production
OPCache
Tambahkan file opcache.ini dan copy via Dockerfile:
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini
Gunakan Versi Image Spesifik
Jangan pakai php:8.3-fpm di production — pakai digest SHA untuk memastikan build yang sama setiap saat:
FROM php:8.3-fpm@sha256:abc123...
Non-Root User
Jalankan PHP-FPM sebagai user non-root:
RUN addgroup --gid 1000 appgroup && adduser --uid 1000 --gid 1000 appuser
USER appuser
5. Setup untuk Laravel
Setelah container berjalan, install Laravel:
docker compose exec app composer create-project laravel/laravel .
docker compose exec app php artisan key:generate
Update .env untuk koneksi database:
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=app_db
DB_USERNAME=app_user
DB_PASSWORD=app_password
Host database adalah db — nama service di docker-compose.yml. Docker Compose otomatis membuat DNS internal.
6. Troubleshooting
Nginx return 502 Bad Gateway
- Penyebab: PHP-FPM belum siap saat Nginx mulai
- Solusi:
docker compose restart app - Pencegahan: tambahkan
depends_ondengan healthcheck
Permission denied saat write file
- Penyebab: user di container berbeda dengan user di host (file permission)
- Solusi: set permission folder src:
sudo chown -R 1000:1000 ./src
Laravel: "No application encryption key"
- Penyebab:
.envtidak berisiAPP_KEY - Solusi:
docker compose exec app php artisan key:generate
Container app terus restart
- Cek log:
docker compose logs app - Penyebab umum: typo di Dockerfile, ekstensi PHP tidak terinstall, Composer gagal
Takeaway
Docker Compose mengubah setup PHP + Nginx dari proses 20+ langkah manual menjadi satu file YAML dan satu perintah up -d.
Keuntungan utama:
- Environment identik di development, staging, dan production
- Onboarding developer baru: clone repo,
docker compose up -d, selesai - Pindah server: pindahkan folder + docker-compose.yml, jalankan, semua jalan
- Tidak ada konflik versi PHP antar proyek