Tiga Bug Host vs Container di Laravel Docker
Waktu deploy aplikasi portofolio saya sendiri (Laravel 12 + Filament 3) ke dalam Docker, saya ketemu tiga bug yang awalnya kelihatan tidak berhubungan sama sekali. Satu soal ekstensi gambar yang tiba-tiba rusak, satu lagi aplikasi langsung 500 tanpa log sama sekali, dan yang terakhir container production menolak boot. Setelah saya telusuri satu-satu, ternyata ketiganya akar masalahnya sama: saya diam-diam mencampur "dunia host" (Mac saya) dengan "dunia container", padahal keduanya harus diperlakukan sebagai dua environment yang sepenuhnya terpisah.
Bug 1: Pembersihan build yang kebablasan di image Alpine
Base image PHP saya pakai Alpine Linux, yang terkenal ramping karena banyak dependency dipisah jadi paket runtime dan paket -dev (dipakai cuma saat kompilasi ekstensi). Di akhir Dockerfile saya taruh langkah "bersih-bersih" untuk membuang paket -dev supaya image tetap kecil.
Masalahnya, perintah purge yang saya tulis ternyata ikut menyapu paket runtime-nya juga, bukan cuma versi -dev. Akibatnya ekstensi image processing yang dipakai Laravel untuk resize gambar jadi rusak total begitu container jalan di production — fitur upload dan thumbnail langsung error.
Fix-nya cukup sederhana setelah ketemu akarnya: install eksplisit paket runtime dan paket -dev di awal, build ekstensi yang dibutuhkan, baru di tahap akhir purge hanya paket -dev-nya secara eksplisit satu per satu — bukan pakai pola yang bisa menyapu semua paket dengan nama mirip.
Bug 2: Artisan yang dijalankan dari PHP host
Bug kedua ini yang paling menjebak. Saya biasa menjalankan perintah artisan — cache config, optimize, dan semacamnya — langsung dari PHP yang terinstall di Mac saya, karena rasanya lebih cepat daripada masuk ke container dulu. Suatu kali setelah menjalankan cache dari host, aplikasi langsung menampilkan error 500 polos, tanpa satu baris pun muncul di log Laravel.
Setelah saya bandingkan, ternyata masalahnya path absolut. Laravel meng-cache konfigurasi dengan path absolut ke direktori aplikasinya, dan path di Mac saya berbeda dari path di dalam container. Begitu container membaca cache yang dibuat dari host, ia gagal resolve path sebelum error handler Laravel sendiri sempat menyala — jadi tidak ada log yang terekam, cuma layar putih.
Sejak itu aturannya jadi kaku: semua perintah PHP dan artisan untuk project ini wajib lewat docker exec ke dalam container, sekecil apa pun perintahnya. Tidak ada pengecualian "cuma cek versi" atau semacamnya.
docker exec php php artisan config:cache
docker exec php php artisan optimize
Bug 3: Composer host meresolve versi yang tidak cocok
Bug ketiga muncul justru saat saya sedang menambal masalah lain. Saya jalankan composer update memakai PHP di host, yang versinya lebih baru dari PHP di dalam container production. Composer, dengan cerdiknya, meresolve dependency yang mensyaratkan versi PHP lebih baru dari yang tersedia di container. Hasilnya: container production menolak boot sama sekali setelah deploy.
Solusi sementaranya adalah mengunci eksplisit target versi PHP di file composer.json, lalu mengulang proses update dari dalam container. Tapi itu masih rawan terulang kalau saya lupa. Fix permanennya, saya bikin image PHP sendiri yang menyalin binary Composer resmi langsung ke dalamnya — jadi perintah composer di dalam container selalu berjalan dengan versi PHP yang sama persis dengan production. Tidak ada lagi celah untuk tidak sengaja pakai Composer atau PHP dari host.
Pelajaran yang saya bawa
Ketiga bug ini sebenarnya satu pelajaran yang sama diulang tiga kali: host dan container itu dua dunia yang harus dijaga tetap terpisah, sekecil apa pun godaan untuk "pinjam" tool dari host karena lebih cepat. Sekarang aturan saya sederhana — kalau itu untuk project di dalam container, semua perintahnya juga harus dijalankan dari dalam container, titik. Dan untuk image Docker sendiri, setiap langkah pembersihan yang menghapus paket harus eksplisit per nama, bukan pola umum yang bisa menyapu lebih dari yang dimaksud.