Seluruh perjalanan section ini — konfigurasi produksi, environment variable, secrets, PHP-FPM, Docker, workflow, health check, migrasi database, hingga zero-downtime — bermuara pada satu momen: hari aplikasi pertama kali melayani pengguna sungguhan. Production readiness checklist adalah gerbang kualitas terakhir yang memastikan tidak ada aspek penting yang terlewat pada momen tersebut. Pada chapter penutup ini kita susun checklist per kategori dengan rujukan ke chapter pembahasnya, lalu melengkapi checklist tersebut dengan tiga alat kerja yang bisa dieksekusi: runbook pra-rilis, smoke test otomatis pasca-deploy, dan sanity check kapasitas.
Cara Menggunakan Checklist Ini #
Checklist yang hanya dibaca sekali tidak melindungi apa pun. Perlakukan setiap kategori berikut sebagai gerbang yang harus lulus sebelum tombol rilis ditekan: jalankan pemeriksaan berurutan, catat tanggal dan hasilnya, dan tetapkan pemilik untuk setiap item yang gagal. Di tim kecil, pemilik tersebut mungkin selalu orang yang sama — tidak masalah, yang penting keputusannya eksplisit, bukan “kira-kira sudah aman”. Checklist juga bukan dokumen sekali-jadi: setiap insiden produksi seharusnya melahirkan baris baru di kategori yang relevan.
Konfigurasi #
| Pemeriksaan | Kondisi lulus | Lihat |
|---|---|---|
display_errors mati | Galat tidak pernah tercetak ke layar pengguna | Production Configuration |
| Nilai per environment terpisah | Konfigurasi dev/staging/produksi lewat environment variable, bukan edit kode | Environment Variables |
| Secrets di luar repositori | Kredensial dari secret manager atau .env yang di-ignore | Managing Secrets |
| Timezone dan locale eksplisit | Tidak bergantung default server | Production Configuration |
Keamanan #
| Pemeriksaan | Kondisi lulus | Lihat |
|---|---|---|
| Escaping output | Semua output dinamis melewati htmlspecialchars() | Output Escaping |
| Query berparameter | Tidak ada penyusunan SQL lewat penggabungan string | SQL Injection |
| Perlindungan CSRF | Form yang mengubah state memakai token | Cross-Site Request Forgery |
| Security headers terpasang | X-Frame-Options, Content-Security-Policy dikonfigurasi | Security Headers |
| Dependensi bebas celah | composer audit bersih di CI | Dependency Security |
Performa #
| Pemeriksaan | Kondisi lulus | Lihat |
|---|---|---|
| Opcode cache aktif | OPcache menyala dengan konfigurasi produksi | Opcode Cache |
| Bebas query N+1 | Halaman utama tidak memicu ratusan query | Query Optimization |
| Cache layer dipasang | Data yang jarang berubah dilayani dari cache | Application Caching |
| Baseline performa tercatat | Angka latensi normal diketahui sebagai pembanding | Benchmarking |
Observability #
| Pemeriksaan | Kondisi lulus | Lihat |
|---|---|---|
| Log terstruktur | Galat masuk log dengan konteks yang bisa dicari | Structured Logging |
| Error reporting produksi | Level tepat, detail teknis tidak bocor ke pengguna | Error Reporting Configuration |
| Metrik kunci terekam | RPS, latensi, dan error rate terlihat di dashboard | Application Metrics |
| Jalur diagnosis siap | Runbook insiden dan production diagnostics tersedia | Production Diagnostics |
Deployment #
| Pemeriksaan | Kondisi lulus | Lihat |
|---|---|---|
| Workflow berulang | Deploy dijalankan satu perintah dari CI, bukan manual | Deployment Workflows |
| Health check hidup | /healthz dipantau load balancer | Health Checks |
| Shutdown anggun | SIGTERM tidak memutus request berjalan | Graceful Shutdown |
| Strategi rilis dipilih | Symlink/blue-green/canary diputuskan dan terdokumentasi | Zero-Downtime Deployment |
Data #
| Pemeriksaan | Kondisi lulus | Lihat |
|---|---|---|
| Backup otomatis + restore teruji | Backup rutin berjalan dan pernah benar-benar di-restore | Database Deployment |
| Migrasi kompatibel | Mengikuti pola expand-contract, bisa di-rollback | Database Deployment |
| Data uji dibersihkan | Akun demo dan data dummy tidak ikut ke produksi | — |
Runbook Pra-Rilis #
Checklist menjawab “sudah siap atau belum”; runbook menjawab “apa langkah berikutnya”. Urutan berikut adalah kerangka yang bisa Anda sesuaikan, dengan asumsi strategi rilis dari chapter sebelumnya sudah dipilih:
- Bekukan perubahan — tidak ada commit baru yang masuk rilis setelah titik ini.
- Cadangkan database dan pastikan salinan tersebut pernah diuji restore-nya, bukan hanya berhasil dibuat.
- Deploy artefak ke lingkungan target tanpa mengaktifkannya — rilis duduk di direktori atau lingkungan idle.
- Jalankan tahap migrasi expand saja — penambahan struktur baru yang tidak merusak versi lama.
- Aktifkan rilis lewat mekanisme yang dipilih (symlink flip atau router flip).
- Jalankan smoke test otomatis — bagian berikutnya membangun alatnya.
- Pantau metrik 30–60 menit pertama dengan ambang rollback yang sudah tertulis sebelum rilis, bukan diputuskan saat panik.
- Jadwalkan tahap contract (pembersihan struktur lama) untuk rilis berikutnya, bukan hari ini.
Smoke Test Setelah Deploy #
Smoke test adalah pemeriksaan pasca-deploy yang menjawab satu pertanyaan: apakah fungsi paling vital aplikasi ini hidup? Bedakan smoke test dari test suite lengkap — smoke test sengaja hanya menyentuh beberapa endpoint kritis (health, beranda, satu endpoint bisnis, satu aset statis) dan harus selesai dalam hitungan detik. Alat berikut memodelkan runner-nya sebagai fungsi murni dengan respons yang disuntikkan, sehingga logika keputusannya teruji tanpa web server:
<?php
function cekEndpoint(array $target, ?array $respons): array
{
if ($respons === null) {
return ["path" => $target["path"], "lulus" => false, "alasan" => "tidak ada respons (timeout)"];
}
if ($respons["status"] !== 200) {
return ["path" => $target["path"], "lulus" => false, "alasan" => "HTTP " . $respons["status"]];
}
if (!str_contains($respons["body"], $target["harusBerisi"])) {
return ["path" => $target["path"], "lulus" => false, "alasan" => "penanda hilang: \"{$target['harusBerisi']}\""];
}
return ["path" => $target["path"], "lulus" => true, "alasan" => "ok"];
}
function jalankanSmokeTest(array $targetList, array $responsMap): bool
{
$semuaLulus = true;
foreach ($targetList as $target) {
$hasil = cekEndpoint($target, $responsMap[$target["path"]] ?? null);
printf("%s %-18s %s\n", $hasil["lulus"] ? "[LULUS]" : "[GAGAL]", $hasil["path"], $hasil["alasan"]);
if (!$hasil["lulus"]) {
$semuaLulus = false;
}
}
return $semuaLulus;
}
$targetList = [
["path" => "/healthz", "harusBerisi" => "ok"],
["path" => "/", "harusBerisi" => "Toko Budi"],
["path" => "/api/produk", "harusBerisi" => "Kopi Gayo"],
["path" => "/static/logo.png", "harusBerisi" => "PNG"],
];
$responsMap = [
"/healthz" => ["status" => 200, "body" => "{\"status\":\"ok\"}"],
"/" => ["status" => 200, "body" => "<h1>Selamat datang di Toko Budi</h1>"],
"/api/produk" => ["status" => 500, "body" => "Internal Server Error"],
"/static/logo.png" => null,
];
$semuaLulus = jalankanSmokeTest($targetList, $responsMap);
echo $semuaLulus
? "SMOKE TEST LULUS - aman melanjutkan rilis\n"
: "SMOKE TEST GAGAL - jalankan rollback sekarang\n";Output yang Dihasilkan:
[LULUS] /healthz ok
[LULUS] / ok
[GAGAL] /api/produk HTTP 500
[GAGAL] /static/logo.png tidak ada respons (timeout)
SMOKE TEST GAGAL - jalankan rollback sekarangBedah singkat: setiap target didefinisikan sebagai pasangan path dan harusBerisi — penanda teks yang wajib ada di body. Pemeriksaan dilakukan berlapis: respons null berarti timeout/koneksi gagal, kode selain 200 langsung gugur, dan body yang tidak memuat penanda dianggap “hidup tapi salah”. Skenario simulasi sengaja memuat dua kegagalan — endpoint API yang meledak dengan HTTP 500 dan aset statis yang tidak merespons — untuk membuktikan runner benar-benar menangkap masalah, bukan selalu menyapa lulus. Nilai kembalian bool membuat runner mudah disambungkan ke pipeline CI: exit code bukan nol berarti deploy otomatis di-rollback.
Untuk runner sungguhan, satu-satunya bagian yang berubah adalah sumber respons — HTTP call nyata menggantikan map simulasi:
<?php
function ambilUrl(string $url): ?array
{
$context = stream_context_create([
"http" => ["timeout" => 5, "ignore_errors" => true],
]);
$body = @file_get_contents($url, false, $context);
if ($body === false) {
return null;
}
preg_match("#HTTP/\S+\s+(\d{3})#", $http_response_header[0] ?? "", $cocok);
return ["status" => (int) ($cocok[1] ?? 0), "body" => $body];
}
// $responsMap = array_map(
// fn(string $path) => ambilUrl("https://toko.example.com" . $path),
// array_column($targetList, "path"),
// );
Opsi ignore_errors membuat file_get_contents() tetap mengembalikan body untuk respons 4xx/5xx alih-alih false, sehingga kode status bisa dibaca dari $http_response_header dan logika cekEndpoint() yang sudah teruji bekerja tanpa perubahan.
Sanity Check Beban Sebelum Membuka Pintu #
Checklist dan smoke test menjawab kebenaran fungsi; pertanyaan terakhir sebelum rilis adalah kebenaran kapasitas. Perhitungannya memakai hukum Little: jumlah worker yang sibuk kira-kira sama dengan laju request dikali latensi rata-rata. Bagi hasil tersebut dengan jumlah worker yang tersedia untuk mendapatkan utilisasi — angka di bawah 70% menyisakan ruang untuk lonjakan:
<?php
function cekKapasitas(array $profil): array
{
$utilisasi = ($profil["rpsPuncak"] * $profil["latensiRataDetik"]) / $profil["worker"];
return [
"utilisasi" => round($utilisasi, 2),
"aman" => $utilisasi < 0.7,
];
}
foreach ([
"weekday pagi" => ["rpsPuncak" => 200.0, "latensiRataDetik" => 0.25, "worker" => 100],
"flash sale" => ["rpsPuncak" => 320.0, "latensiRataDetik" => 0.25, "worker" => 100],
] as $nama => $profil) {
$hasil = cekKapasitas($profil);
printf(
"%-15s utilisasi=%.2f -> %s\n",
$nama,
$hasil["utilisasi"],
$hasil["aman"] ? "kapasitas cukup" : "RAWAN: tambah worker atau turunkan latensi"
);
}Output yang Dihasilkan:
weekday pagi utilisasi=0.50 -> kapasitas cukup
flash sale utilisasi=0.80 -> RAWAN: tambah worker atau turunkan latensiPada skenario weekday, 200 request per detik dengan latensi rata-rata 0,25 detik menempati 50 worker — tepat setengah dari 100 worker yang tersedia, utilisasi 0,50, masih aman. Skenario flash sale menaikkan beban menjadi 320 RPS sehingga utilisasi menyentuh 0,80: di atas ambang 70%, dan satu-satunya jalan keluarnya menambah worker atau menurunkan latensi (biasanya lewat cache). Angka latensi untuk perhitungan ini berasal dari baseline benchmark yang sudah Anda catat di kategori Performa — inilah contoh bagaimana baris checklist saling menopang.
Latihan Rollback Sebelum Dibutuhkan #
Rollback yang belum pernah dilatih adalah harapan, bukan rencana. Luangkan waktu sebelum hari rilis untuk menjalankan rollback penuh di staging: ukur berapa lama sampai versi lama kembali melayani, periksa apakah data yang ditulis versi baru tetap terbaca oleh versi lama (ujian sesungguhnya dari disiplin expand-contract), dan pastikan orang yang bertugas tahu perintah apa yang dijalankan. Catat angka waktunya — “rollback 90 detik” yang terukur memberi keberanian mengambil keputusan cepat saat insiden, sedangkan rollback yang diperkirakan “cepat kok” hampir selalu lambat.
Ringkasan #
Production readiness adalah sintesis dari seluruh disiplin section ini: konfigurasi dan keamanan yang benar, performa yang terukur, observability yang bisa menjawab pertanyaan, deployment yang berulang dan aman, serta data yang selalu bisa dikembalikan. Checklist menjadikannya gerbang yang eksplisit, runbook menjadikannya urutan yang bisa diikuti, smoke test menjadikannya verifikasi otomatis, dan latihan rollback menjadikannya jaring pengaman yang teruji. Dengan gerbang ini terlewati, aplikasi PHP Anda siap menghadapi pengguna sungguhan — dan saat aplikasi tumbuh besar, struktur internalnya sendiri akan menjadi tantangan berikutnya; di situlah Section Framework Development mengambil alih.