Rilis pertama ke production terasa seperti petualangan; rilis kesepuluh harus terasa seperti menekan satu tombol. Jarak antara keduanya adalah workflow: urutan langkah yang sama, dieksekusi dengan cara yang sama, dengan hasil yang bisa diverifikasi setiap saat. Pada chapter ini kita susun urutan lengkap sebuah rilis Laravel — dari maintenance mode hingga smoke test — lalu membuktikannya sebagai skrip yang benar-benar kita jalankan, bukan sekadar daftar saran. Seluruh blok bangunan sudah kita kuasai satu per satu di chapter-chapter sebelumnya; tugas sekarang adalah merangkainya.
Anatomi Satu Rilis #
Urutan berikut adalah kerangka yang dipakai hampir semua aplikasi Laravel. Delapan langkahnya sengaja berurutan — beberapa di antaranya hanya aman dilakukan pada posisinya:
| # | Langkah | Perintah inti | Mengapa di posisi itu |
|---|---|---|---|
| 1 | Aktifkan maintenance | php artisan down | Pengguna tidak melihat setengah versi |
| 2 | Ambil kode baru | git pull origin main | Kode sebelum migrasi, karena migrasi ikut kode baru |
| 3 | Pasang dependency | composer install --no-dev --optimize-autoloader | Autoloader baru untuk kode baru |
| 4 | Build aset frontend | npm ci && npm run build | Aset baru tersedia sebelum halaman ditampilkan |
| 5 | Migrasi database | php artisan migrate --force | Skema baru siap sebelum kode aktif |
| 6 | Bangun ulang cache | optimize:clear + optimize | Cache lama tidak boleh menyembunyikan kode baru |
| 7 | Restart worker | php artisan queue:restart | Worker memuat kode lama di memori |
| 8 | Buka aplikasi + verifikasi | php artisan up + smoke test | Bukti akhir bahwa rilis sehat |
Langkah 5 dan 6 adalah jantung keamanan rilis: migrasi produksi dengan strategi kolom baru sudah dibahas di Database Migration di Production
, sementara alasan membangun ulang cache sudah dibuktikan di Caching di Production
. Langkah 7 tanpa queue:restart membuat job dieksekusi memakai kode yang sudah tidak ada di disk — sinyal restart-nya sudah kita lihat bekerja di Queue Worker di Production
.
Skrip Mini yang Benar-Benar Dijalankan #
Daftar di atas hanya berguna bila menjadi skrip. Versi mini berikut kita jalankan utuh di sandbox; di server, tiga langkah pembuka disisipkan sebelum baris php artisan down — git pull origin main, composer install --no-dev --optimize-autoloader --no-interaction, dan npm ci && npm run build — karena sandbox tidak punya remote Git dan Node:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/var/www/tokobudi"
cd "$APP_DIR"
echo "== 1. maintenance mode ON =="
php artisan down
echo "== 2. migrasi database =="
php artisan migrate --force
echo "== 3. bangun ulang cache =="
php artisan optimize:clear
php artisan optimize
echo "== 4. restart queue worker =="
php artisan queue:restart
echo "== 5. pastikan storage:link =="
php artisan storage:link
echo "== 6. maintenance mode OFF =="
php artisan upBaris set -euo pipefail adalah pengaman skrip: -e menghentikan eksekusi begitu satu langkah gagal (migrasi gagal → aplikasi tidak dibuka), -u menolak variabel kosong, dan -o pipefail menangkap kegagalan di tengah pipe. Hasil eksekusinya di sandbox:
== 1. maintenance mode ON ==
INFO Application is now in maintenance mode.
== 2. migrasi database ==
INFO Nothing to migrate.
== 3. bangun ulang cache ==
INFO Clearing cached bootstrap files.
config ......................................................... 0.89ms DONE
cache ........................................................ 10.14ms DONE
compiled ....................................................... 0.76ms DONE
events ......................................................... 0.48ms DONE
routes ......................................................... 0.49ms DONE
views .......................................................... 2.80ms DONE
INFO Caching framework bootstrap, configuration, and metadata.
config ......................................................... 7.10ms DONE
events ......................................................... 0.94ms DONE
routes ......................................................... 8.63ms DONE
views .......................................................... 15.64ms DONE
== 4. restart queue worker ==
INFO Broadcasting queue restart signal.
== 5. pastikan storage:link ==
ERROR The [public/storage] link already exists.
== 6. maintenance mode OFF ==
INFO Application is now live. Dua detail dari output layak dijelaskan. Nothing to migrate bukan skrip gagal — migrasi memang hanya berjalan bila ada migration baru, dan sisanya adalah keadaan normal rilis biasa. Lalu baris ERROR The [public/storage] link already exists tampak menakutkan padahal jinak: tautan sudah dibuat rilis sebelumnya, storage:link bersifat idempoten, dan exit code tetap 0 sehingga skrip lanjut ke langkah penutup.
Skrip yang sama juga menjawab pertanyaan klasik “apa yang dilihat pengguna selama maintenance?”. Selama mode aktif kita ukur dua endpoint:
GET /: HTTP 503
GET /up: HTTP 200Pengguna melihat halaman 503 dengan status maintenance, sementara health check tetap hijau — kombinasi yang sudah dibedah di Logging dan Monitoring .
Smoke Test Pasca-Deploy #
Langkah terakhir tabel tadi menuntut bukti, bukan kepercayaan. Smoke test adalah tiga-empat curl yang memastikan halaman-halaman inti menjawab sebagaimana mestinya; URL dasar dikirim sebagai argumen pertama:
#!/usr/bin/env bash
BASE_URL="${1:-https://tokobudi.example.com}"
GAGAL=0
periksa() {
local nama="$1" url="$2" diharapkan="$3"
local status
status=$(curl -s -o /dev/null -w '%{http_code}' "$url")
if [ "$status" = "$diharapkan" ]; then
echo "LULUS $nama ($url) -> $status"
else
echo "GAGAL $nama ($url) -> $status (diharapkan $diharapkan)"
GAGAL=1
fi
}
periksa "health check" "$BASE_URL/up" 200
periksa "halaman utama" "$BASE_URL/" 200
periksa "halaman tidak ditemukan" "$BASE_URL/tidak-ada" 404
exit $GAGALKita jalankan terhadap server sandbox:
bash smoke.sh http://127.0.0.1:8018Output yang Dihasilkan:
LULUS health check (http://127.0.0.1:8018/up) -> 200
LULUS halaman utama (http://127.0.0.1:8018/) -> 200
LULUS halaman tidak ditemukan (http://127.0.0.1:8018/tidak-ada) -> 404Exit code 0 berarti seluruh pemeriksaan lulus. Karena skrip berbicara lewat exit code, smoke test ini bisa langsung disandarkan di akhir skrip deploy maupun pipeline CI — rilis yang gagal smoke test otomatis ditandai gagal.
Tier Otomasi: Manual, Skrip, Pipeline #
Skrip di atas disimpan sebagai deploy.sh di server dan dieksekusi lewat SSH — tier pertama otomasi. Tier berikutnya memindahkan pemicunya dari tangan ke Git: pipeline CI/CD menjalankan skrip yang sama setiap kali tag rilis didorong. Contoh tampilan pipeline GitHub Actions:
name: Deploy
on:
push:
tags: ["v*"]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Jalankan deploy di server
uses: appleboy/ssh-action@v4
with:
host: ${{ secrets.DEPLOY_HOST }}
username: deploy
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: bash /var/www/tokobudi/deploy.shKredensial server hidup di secrets repository, bukan di skrip — prinsip yang sama dengan Secret Management . Rekomendasi praktisnya: jangan lompat ke tier pipeline sebelum skrip manual terbukti stabil berpuluh kali.
Deploy Atomik: Tanpa Jendela Maintenance #
Skrip kita memakai maintenance mode, sehingga ada jendela waktu (detik hingga menit) ketika pengguna ditolak. Pola atomic deploy menghapus jendela itu lewat struktur direktori — contoh tampilan:
/var/www/tokobudi/
├── releases/
│ ├── 2026-10-13-0900/
│ └── 2026-10-14-1130/
├── shared/
│ ├── .env
│ └── storage/
└── current -> releases/2026-10-14-1130Rilis baru dibangun utuh di dalam releases/<versi>/ — kode, dependency, aset, migrasi — sementara current yang ditunjuk document root Nginx masih menunjuk rilis lama yang terus melayani request. Saat semuanya siap, pergantian hanyalah satu perintah: ln -sfn releases/2026-10-14-1130 current — pembuatan symlink bersifat atomik di level filesystem, sehingga tidak ada request yang menangkap setengah versi. .env dan storage/ ditaruh di shared/ dan di-symlink ke tiap rilis agar upload dan konfigurasi tidak ikut terganti. Manfaat sampingannya besar: direktori rilis lama tetap utuh, dan “rollback” tinggal membalik arah symlink — cerita lengkapnya di Rollback dan Recovery
.
Alur rilis kini berulang dan teruji. Pertanyaan berikutnya adalah rencana B: apa yang kita lakukan ketika rilis yang baru saja dianggap sehat itu ternyata bermasalah — dibahas tuntas di Rollback dan Recovery .