Laravel blue-green deploy cocok dipakai saat Anda ingin merilis perubahan aplikasi tanpa memutus trafik produksi lebih lama dari yang diperlukan, sekaligus menyiapkan rollback yang cepat bila rilis baru bermasalah. Intinya, Anda menyiapkan dua lingkungan yang setara: satu aktif melayani trafik, satu lagi menjadi target rilis baru. Setelah verifikasi lulus, trafik dipindahkan ke lingkungan baru dengan perubahan yang kecil dan terkontrol.

Pada aplikasi Laravel, pendekatan ini tidak cukup berhenti di pergantian folder atau upstream Nginx. Anda juga harus memastikan .env, cache konfigurasi, queue worker, migrasi database, dan storage bersama diperlakukan dengan benar. Jika salah satu komponen ini tidak sinkron, deploy bisa terlihat sukses tetapi request produksi tetap gagal.

Tujuan dan prinsip kerja blue-green deployment

Pada pola blue-green, ada dua versi lingkungan aplikasi:

  • Blue: versi yang sedang aktif menerima trafik.
  • Green: versi baru yang sudah dibangun, diuji, dan siap menerima trafik.

Keuntungan utamanya:

  • Downtime minim, karena proses build, install dependency, warm-up cache, dan verifikasi dilakukan di lingkungan pasif.
  • Rollback cepat, karena trafik bisa dikembalikan ke versi sebelumnya tanpa perlu build ulang.
  • Rilis lebih aman, karena Anda punya titik verifikasi yang jelas sebelum dan sesudah cutover.

Namun ada trade-off yang perlu dipahami:

  • Membutuhkan disiplin pengelolaan state, terutama database, queue, cache, dan file upload.
  • Butuh struktur direktori dan otomasi deploy yang rapi.
  • Tidak semua perubahan database bisa di-rollback dengan mudah, sehingga strategi migrasi harus dirancang konservatif.

Arsitektur rilis yang praktis untuk Laravel

Struktur direktori release dan shared storage

Untuk Laravel, pola yang umum dan aman adalah memisahkan direktori rilis dari data bersama:

/var/www/myapp/
├── current -> /var/www/myapp/releases/20240612-153000
├── releases/
│   ├── 20240612-153000/
│   ├── 20240612-160500/
│   └── ...
└── shared/
    ├── .env
    ├── storage/
    └── bootstrap/cache/

Prinsipnya:

  • Setiap deploy membuat folder baru di releases/.
  • Symlink current menunjuk ke release aktif.
  • .env dan storage ditempatkan di shared/ agar tidak hilang saat ganti release.
  • Cache framework dapat diperlakukan hati-hati: sebagian tim menyimpan bootstrap/cache per release, sebagian lain menyiapkan direktori bersama. Yang penting adalah konsistensi dan memastikan cache dibangun ulang dari environment yang benar.

Untuk aplikasi yang menerima upload file lokal, direktori storage/app harus berada di shared/storage. Jika tidak, file yang diunggah pada release lama bisa “hilang” dari release baru hanya karena path berubah. Bila memungkinkan, file user lebih aman dipindahkan ke object storage agar pemisahan release menjadi lebih sederhana.

Alur deploy tingkat tinggi

  1. Checkout kode ke folder release baru.
  2. Install dependency produksi.
  3. Link .env dan storage dari shared/.
  4. Build cache yang diperlukan.
  5. Jalankan migrasi yang aman untuk kompatibilitas dua versi.
  6. Lakukan health check dan smoke test ke release baru.
  7. Alihkan trafik dengan mengganti symlink current atau upstream Nginx.
  8. Reload PHP-FPM/worker yang perlu membaca path baru.
  9. Pantau log, error rate, latency, dan queue backlog.
  10. Jika ada masalah, rollback dengan mengembalikan symlink atau upstream ke release sebelumnya.

Implementasi dasar dengan Nginx, symlink release, dan Artisan

Contoh konfigurasi Nginx

Pendekatan paling sederhana adalah membuat document root selalu menunjuk ke symlink current/public.

server {
    listen 80;
    server_name app.example.com;
    root /var/www/myapp/current/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }

    location ~ /\. {
        deny all;
    }
}

Penggunaan $realpath_root membantu Nginx dan PHP-FPM mengarah ke path final hasil resolusi symlink, sehingga lebih aman saat cutover antar release.

Langkah deploy contoh

Berikut contoh alur shell yang ringkas. Nama path bisa Anda sesuaikan.

APP_DIR=/var/www/myapp
RELEASE_ID=$(date +%Y%m%d-%H%M%S)
RELEASE_DIR=$APP_DIR/releases/$RELEASE_ID
SHARED_DIR=$APP_DIR/shared

mkdir -p $RELEASE_DIR
git clone --depth=1 [email protected]:team/myapp.git $RELEASE_DIR
cd $RELEASE_DIR

composer install --no-dev --prefer-dist --optimize-autoloader

ln -sfn $SHARED_DIR/.env .env
rm -rf storage
ln -sfn $SHARED_DIR/storage storage

php artisan key:generate --show
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan event:clear

php artisan config:cache
php artisan route:cache
php artisan view:cache

php artisan migrate --force

php artisan queue:restart

ln -sfn $RELEASE_DIR $APP_DIR/current
systemctl reload php-fpm
systemctl reload nginx

Beberapa catatan penting:

  • php artisan key:generate --show hanya contoh untuk menampilkan key, bukan dijalankan setiap deploy di produksi. Kunci aplikasi harus stabil dan berasal dari .env bersama.
  • config:cache harus dilakukan setelah .env release terhubung dengan benar. Jika cache dibuat sebelum symlink .env benar, aplikasi bisa menyimpan konfigurasi yang salah.
  • queue:restart dibutuhkan agar worker lama berhenti secara halus dan memuat kode release baru.

Symlink swap vs upstream switch

Ada dua cara umum mengalihkan trafik:

  • Symlink swap: Nginx tetap mengarah ke current/public, lalu symlink current diganti atomik. Cocok untuk satu host atau beberapa host dengan deploy per node.
  • Upstream switch: Blue dan green berjalan sebagai pool terpisah, lalu Nginx atau load balancer mengarahkan trafik ke pool baru. Cocok untuk arsitektur multi-node atau containerized.

Jika Anda baru memulai, symlink swap biasanya paling mudah dioperasikan. Jika butuh cutover bertahap, health check terintegrasi, atau canary, upstream switch lebih fleksibel.

Health check dan smoke test pascadeploy

Syarat health check yang berguna

Health check jangan hanya memeriksa apakah proses PHP hidup. Untuk Laravel, endpoint health yang baik minimal memverifikasi:

  • Aplikasi bisa bootstrap dan merespons HTTP 200.
  • Koneksi database berhasil.
  • Koneksi cache atau Redis berhasil bila komponen tersebut wajib.
  • Versi release atau commit hash bisa ditampilkan untuk validasi.

Contoh respons health yang sederhana:

{
  "status": "ok",
  "release": "20240612-153000",
  "checks": {
    "db": "ok",
    "cache": "ok"
  }
}

Jangan masukkan query berat atau logika bisnis kompleks ke endpoint health. Tujuannya adalah memverifikasi dependensi dasar dengan cepat dan konsisten.

Smoke test setelah deploy

Smoke test adalah verifikasi pascadeploy terhadap alur paling kritis. Pada Laravel, fokuskan ke endpoint dan proses yang paling berpengaruh ke pengguna:

  • Halaman utama atau endpoint API utama.
  • Login atau autentikasi token.
  • Satu query database penting.
  • Akses Redis/cache bila aplikasi bergantung padanya.
  • Pembuatan job ke queue dan pemrosesan satu job ringan jika memungkinkan.

Contoh smoke test via shell:

curl -fsS http://127.0.0.1/health || exit 1
curl -fsS http://127.0.0.1/ || exit 1
curl -fsS http://127.0.0.1/api/ping || exit 1

Jika aplikasi berada di belakang domain publik, sebaiknya sediakan cara menguji release baru sebelum trafik umum dialihkan, misalnya lewat host internal, port khusus, atau upstream sementara.

Praktik yang baik: anggap health check sebagai pemeriksaan kesiapan sistem, sedangkan smoke test adalah verifikasi jalur pengguna paling penting. Keduanya saling melengkapi, bukan saling menggantikan.

Strategi migrasi database yang aman untuk blue-green

Bagian paling berisiko dalam Laravel blue-green deploy biasanya bukan Nginx atau symlink, melainkan perubahan skema database. Saat blue dan green mungkin hidup bersamaan selama masa transisi, skema harus kompatibel dengan dua versi aplikasi.

Prinsip expand-then-contract

Pendekatan yang aman adalah expand-then-contract:

  1. Expand: tambahkan kolom, tabel, atau indeks baru tanpa merusak perilaku versi lama.
  2. Deploy kode baru yang bisa membaca/menulis skema lama dan baru jika perlu.
  3. Setelah semua node stabil dan data termigrasi, lakukan contract: hapus kolom lama atau constraint yang tidak dipakai.

Contoh perubahan yang relatif aman:

  • Menambah kolom nullable.
  • Menambah indeks.
  • Menambah tabel baru yang belum dipakai versi lama.

Perubahan yang perlu ekstra hati-hati:

  • Menghapus kolom yang masih dibaca release lama.
  • Mengganti nama kolom tanpa masa kompatibilitas.
  • Mengubah tipe data yang memutus asumsi kode lama.
  • Migrasi besar yang mengunci tabel terlalu lama.

Kapan menjalankan migrasi

Untuk sebagian besar kasus, migrasi dijalankan pada release baru sebelum cutover, tetapi hanya bila migrasi tersebut kompatibel ke belakang. Jika migrasi berpotensi mengganggu node lama, lebih aman membaginya menjadi beberapa tahap rilis.

Jika Anda punya job yang menulis format data baru, pertimbangkan urutan berikut:

  1. Deploy perubahan skema kompatibel.
  2. Cutover aplikasi baru.
  3. Restart worker queue agar semua producer/consumer konsisten.
  4. Aktifkan fitur penulisan format baru secara bertahap, bila perlu lewat feature flag.

Sinkronisasi queue worker, scheduler, dan cache/config

Queue worker harus memuat release baru

Worker queue Laravel adalah proses yang hidup lama. Jika Anda hanya mengganti symlink release tanpa me-restart worker, proses lama masih bisa menjalankan kode release sebelumnya. Akibatnya:

  • Job memproses class lama yang sudah tidak cocok dengan payload baru.
  • Konfigurasi env lama tetap dipakai.
  • Perbedaan path release memicu error file atau autoload.

Langkah minimum yang umum dilakukan adalah:

php artisan queue:restart

Perintah ini memberi sinyal agar worker keluar secara halus setelah menyelesaikan job aktif. Supervisor atau process manager kemudian akan menyalakan worker baru dari release aktif.

Scheduler hanya boleh aktif di satu tempat

Jika Anda menjalankan scheduler Laravel melalui cron atau process manager, pastikan hanya ada satu eksekutor aktif untuk tugas yang memang harus tunggal. Dalam skema blue-green multi-node, kesalahan umum adalah kedua lingkungan menjalankan scheduler bersamaan setelah cutover. Ini bisa menyebabkan pengiriman email ganda, sinkronisasi data ganda, atau job terduplikasi.

Cache dan konfigurasi harus dibangun dari env yang benar

Laravel banyak bergantung pada cache konfigurasi dan route untuk performa. Masalahnya, cache ini mudah menjadi sumber insiden jika dibangun pada waktu yang salah.

Urutan yang aman secara umum:

  1. Hubungkan .env shared ke release baru.
  2. Bersihkan cache lama yang mungkin tertinggal di release tersebut.
  3. Bangun ulang config:cache, route:cache, dan cache lain yang diperlukan.
  4. Setelah cutover, reload service yang perlu memuat ulang path atau env.

Kesalahan umum:

  • Menjalankan config:cache sebelum .env terpasang.
  • Menyalin file cache dari release lama ke release baru.
  • Mengasumsikan PHP-FPM worker lama akan langsung membaca environment terbaru tanpa reload yang sesuai.

Checklist observability minimum setelah cutover

Setelah trafik dialihkan, jangan langsung menganggap deploy selesai. Minimal, pantau empat sinyal berikut selama beberapa menit pertama:

  • Log aplikasi: cari exception baru, error koneksi DB/Redis, dan error class autoload.
  • Error rate: amati lonjakan HTTP 5xx atau kegagalan endpoint penting.
  • Latency: periksa apakah waktu respons naik tajam setelah cache reload atau query berubah.
  • Queue backlog: pastikan antrean tidak menumpuk karena worker gagal restart atau job error berulang.

Tambahkan juga bila tersedia:

  • Status health endpoint dari load balancer.
  • Jumlah restart worker.
  • Trafik ke dependency eksternal seperti SMTP, payment gateway, atau object storage.

Gunakan release ID atau commit hash di log agar korelasi insiden lebih cepat. Bahkan penambahan field sederhana seperti release=20240612-153000 sering sangat membantu saat membedakan error dari blue vs green.

Studi insiden ringan: deploy sukses, tetapi trafik gagal karena env/cache tidak sinkron

Kronologi singkat

Tim melakukan deploy release baru. Semua langkah tampak berhasil: composer install selesai, migrasi sukses, symlink current sudah berpindah, dan Nginx merespons normal. Namun beberapa detik setelah trafik produksi masuk, endpoint API mulai mengembalikan HTTP 500.

Health check dasar hanya memeriksa HTTP 200 pada route sederhana, sehingga tidak mendeteksi masalah lebih dalam. Setelah dilihat di log, error mengarah ke koneksi Redis yang memakai host lama. Ternyata release baru membangun config:cache sebelum symlink .env shared terpasang, sehingga konfigurasi yang tersimpan bukan environment produksi yang benar.

Deteksi

Gejala yang muncul:

  • Error rate 5xx naik segera setelah cutover.
  • Latency beberapa endpoint meningkat karena retry koneksi.
  • Queue backlog mulai naik karena worker baru gagal terhubung ke Redis.
  • Log menunjukkan nilai host/port yang tidak sesuai environment produksi.

Di titik ini, observability minimum cukup untuk menyimpulkan bahwa deploy berhasil secara mekanis, tetapi gagal secara operasional.

Rollback cepat

Karena struktur release dan symlink sudah dipisah, rollback bisa dilakukan cepat:

  1. Kembalikan symlink current ke release sebelumnya.
  2. Reload PHP-FPM bila perlu agar worker web aktif memakai path lama.
  3. Jalankan php artisan queue:restart agar worker queue kembali ke release sebelumnya.
  4. Verifikasi health check, smoke test, dan queue backlog.

Rollback seperti ini jauh lebih cepat daripada memperbaiki release di tempat pada saat insiden, karena fokus utama adalah memulihkan layanan lebih dahulu.

Root cause

Penyebab utamanya bukan bug bisnis, melainkan urutan deploy yang salah:

  • .env belum tersambung saat cache konfigurasi dibangun.
  • Smoke test tidak memeriksa dependensi Redis/queue.
  • Worker queue direstart setelah cutover, sehingga kegagalan baru terlihat saat trafik nyata masuk.

Tindakan pencegahan

  • Pastikan symlink .env dan storage dipasang sebelum menjalankan perintah cache apa pun.
  • Tambahkan health check yang memverifikasi DB dan Redis, bukan hanya HTTP 200.
  • Perluas smoke test untuk menyentuh jalur yang benar-benar kritis.
  • Sertakan validasi release metadata, misalnya commit hash dan environment aktif.
  • Dokumentasikan urutan deploy yang tidak boleh diubah.

Urutan deploy yang lebih aman

Jika diringkas, alur praktis yang relatif aman untuk Laravel adalah sebagai berikut:

  1. Buat release baru di releases/.
  2. Checkout kode dan install dependency produksi.
  3. Pasang symlink ke shared/.env dan shared/storage.
  4. Bersihkan cache lama pada release tersebut.
  5. Bangun ulang cache konfigurasi, route, dan view.
  6. Jalankan migrasi yang kompatibel ke belakang.
  7. Jalankan health check dan smoke test ke release baru.
  8. Cutover trafik lewat symlink atau upstream switch.
  9. Restart atau reload worker yang relevan: queue, PHP-FPM, scheduler bila perlu.
  10. Pantau log, error rate, latency, dan queue backlog.
  11. Jika ada anomali, rollback segera dan lakukan analisis akar masalah setelah layanan pulih.

Kesalahan yang sering terjadi

  • Menganggap blue-green otomatis aman tanpa memikirkan kompatibilitas database.
  • Tidak memisahkan release dan storage bersama.
  • Melupakan restart queue worker yang hidup lama.
  • Mengandalkan health check yang terlalu dangkal.
  • Tidak menguji jalur autentikasi, cache, atau dependency eksternal dalam smoke test.
  • Menghapus release lama terlalu cepat sebelum masa observasi selesai.

Penutup

Laravel blue-green deploy bukan sekadar mengganti symlink atau upstream. Supaya benar-benar menekan downtime dan mempercepat rollback, Anda perlu merancang alur rilis yang memperhatikan stateful component: database, queue, cache, konfigurasi, dan storage bersama. Dengan health check yang relevan, smoke test pascadeploy, migrasi yang kompatibel, serta observability minimum yang disiplin, risiko deploy gagal di trafik nyata bisa turun signifikan.

Jika Anda baru mulai, gunakan pola sederhana lebih dulu: struktur releases/ + shared/, symlink current, endpoint health yang berguna, dan prosedur rollback yang terlatih. Setelah itu, baru tingkatkan ke cutover berbasis upstream atau load balancer bila kebutuhan operasional Anda bertambah.