Menerbangkan aplikasi PHP ke produksi: setup PHP-FPM + Nginx dengan tuning process manager, konfigurasi environment yang benar, lalu membangun CI/CD dengan GitHub Actions, Docker image PHP, dan monitoring error via Sentry agar rilis aman dan masalah cepat terdeteksi.

Semua keterampilan yang kalian bangun dari episode 0 sampai 18 akan diuji di sini: membawa aplikasi dari laptop kalian ke server produksi yang melayani pengguna sungguhan. Deployment dan DevOps adalah jembatan antara "kode berjalan" dan "kode berjalan dengan andal di produksi".
Mengapa penting? Aplikasi terbaik tidak ada artinya jika tidak bisa di-deploy, di-scale, dan dipantau. Setup PHP-FPM + Nginx adalah standar industri, CI/CD memastikan setiap perubahan teruji sebelum rilis, dan monitoring memastikan masalah ditemukan dalam hitungan menit — bukan dilaporkan pengguna. Mari bangun itu semua.
Nginx (web server) menerima HTTP, PHP-FPM mengeksekusi PHP. Konfigurasi pool PHP-FPM:
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 1000pm)| Mode | Cara kerja | Kapan |
|---|---|---|
static | Jumlah worker tetap | Beban stabil, server khusus |
dynamic | Worker menyesuaikan beban | Default yang baik untuk sebagian besar |
ondemand | Worker dibuat saat dibutuhkan | Server dengan sumber daya terbatas |
pm.max_requests memaksa worker restart setelah N request — mencegah memory leak menumpuk. Aturan kasar: max_children ≈ RAM / (rata-rata memori per request).
Konfigurasi Nginx:
server {
listen 80;
server_name toko.example.com;
root /var/www/toko/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.(?!well-known).* {
deny all;
}
}Poin penting: root mengarah ke folder public/ (hanya index.php yang terekspos, kode src/ tidak pernah diakses langsung), dan deny all memblokir akses file dot (/.env).
Kredensial tidak pernah di kode. Gunakan file .env di luar web root:
APP_ENV=production
APP_DEBUG=false
DB_HOST=127.0.0.1
DB_NAME=toko
DB_USER=toko_app
DB_PASS=rahasia-sangat
REDIS_HOST=127.0.0.1
APP_KEY=base64:...Aplikasi membaca via helper:
$dbHost = getenv("DB_HOST") ?: "127.0.0.1";
$dbPass = getenv("DB_PASS");Aturan penting:
.env tidak pernah di-commit; simpan template sebagai .env.example.APP_DEBUG=false di produksi — stack trace tidak boleh tampil (episode 17).Kontainer membuat deployment reproducible:
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --prefer-dist --no-interaction
FROM php:8.5-fpm
RUN docker-php-ext-install pdo_mysql opcache \
&& docker-php-ext-enable opcache
COPY --from=vendor /app/vendor /var/www/vendor
COPY . /var/www
WORKDIR /var/www
RUN chown -R www-data:www-data storage bootstrap/cache
USER www-dataservices:
app:
build: .
env_file: .env
depends_on:
- db
volumes:
- ./public:/var/www/public:ro
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: ${DB_NAME}
MYSQL_PASSWORD: ${DB_PASS}docker-php-ext-install mengkompilasi ekstensi ke image — opcache aktif otomatis (episode 18). Multi-stage build (vendor lalu runtime) membuat image kecil dan tidak membawa tool build.
Pipeline: setiap push → lint, test, build → deploy:
name: CI
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.5"
coverage: xdebug
- run: composer install --no-interaction
- run: vendor/bin/phpunit
- run: vendor/bin/phpstan analyse src --level=8
- run: composer audit
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t ${{ vars.REGISTRY }}/toko:${{ github.sha }} .
- run: docker push ${{ vars.REGISTRY }}/toko:${{ github.sha }}
- run: ssh deploy@server "docker pull ... && docker compose up -d"Langkah-langkah ini menjalankan seluruh jaring pengaman dari episode 10 (phpunit + PHPStan) dan episode 17 (composer audit) di setiap push — tidak ada kode yang lolos tanpa teruji.
Tip
Saat deploy dengan opcache: jika opcache.validate_timestamps=1 (default), kode baru otomatis terdeteksi dalam revalidate_freq. Jika di-set 0 untuk performa maksimal, wajib php-fpm restart (atau opcache_reset()) setelah deploy — kalau tidak, aplikasi tetap menjalankan kode lama.
Setelah produksi, kalian butuh mata: Sentry mengumpulkan exception dengan konteks lengkap (stack trace, user, URL, kode):
Sentry\init([
"dsn" => "https://...@sentry.io/123",
"traces_sample_rate" => 0.2,
]);
// exception otomatis terkirim; kirim manual untuk konteks
try {
$pembayaran->proses($total);
} catch (PaymentFailedException $e) {
Sentry\captureException($e, [
"user" => ["id" => $userId],
"extra" => ["order_id" => $orderId],
]);
}Selain Sentry, pasang health check sederhana dan monitoring infrastruktur (metrik PHP-FPM, Nginx access log, database slow query). Tujuan: masalah ditemukan oleh alert, bukan oleh keluhan pengguna.
root mengarah ke folder proyek, bukan public/ — kode .env dan src/ terekspos.pm.max_children terlalu besar — server OOM saat beban tinggi; hitung dari RAM.APP_DEBUG=true di produksi — stack trace bocor (episode 17).Inti yang harus dibawa pulang:
root → public/, pahami pm mode, atur max_requests untuk mencegah leak..env di luar web root; APP_DEBUG=false di produksi.phpunit, PHPStan, composer audit, lalu deploy otomatis.Di episode 20 selanjutnya kita mengulik batas PHP tradisional: Async, Queues & Streaming — Fibers dan Swoole untuk concurrency, Laravel Queues dengan Redis/SQS dan worker, jadwal background jobs lewat cron/schedule, serta streaming response. PHP ternyata bisa lebih dari sekadar "request-response" klasik.