Di lokal, aplikasi Laravel cukup dijalankan dengan php artisan serve dan hampir semua terlihat beres. Situasi berubah begitu repository sudah ada di GitHub dan VPS baru menyala: deploy Laravel dari GitHub ke VPS bukan soal menyalin folder proyek ke server. Rantai langkah tambahan inilah yang sering membuat developer pemula bingung.
Beberapa bagian aplikasi memang sengaja tidak ikut ke git. Folder vendor/ harus diinstall ulang lewat Composer di server, file .env berisi kredensial lingkungan yang berbeda tiap server, dan aset hasil build Vite ada di public/build yang juga masuk .gitignore. Di sisi server, document root Nginx harus menunjuk ke folder public/, direktori storage dan bootstrap/cache butuh permission tulis, database harus dimigrasi, dan queue worker adalah proses panjang yang tidak otomatis melihat kode baru.
Panduan ini membedah dua jalur sekaligus:
- Jalur manual —
git pulldi server plus checklist post-deploy, supaya kalian paham apa yang sebenarnya terjadi di setiap rilis. - Jalur GitHub Actions — tes dan build aset berjalan di CI, hasil build dikirim ke server, lalu deploy via SSH otomatis setiap push ke
main.
Alurnya kira-kira begini:
developer yang sudah bisa membuat aplikasi Laravel dan push ke GitHub, tapi baru pertama kali menyentuh server Linux. Setelah selesai, aplikasi bisa diakses lewat domain, dan rilis berikutnya — kalau memakai Actions — cukup dengan git push.
Semua contoh ditulis untuk Laravel 13, PHP 8.3–8.4, Node 22, dan Ubuntu 24.04 per September 2026. Panduan ini valid untuk Laravel 12 dan 13; bedanya hanya versi PHP minimum (Laravel 12 minimal PHP 8.2, Laravel 13 minimal PHP 8.3).
Prasyarat dan Rencana Arsitektur
Apa saja yang dibutuhkan
- Repository GitHub berisi aplikasi Laravel. Pastikan
composer.lockdanpackage-lock.jsonikut di-commit — tanpa keduanya, versi dependency bisa berbeda tiap deploy. - VPS Ubuntu 24.04 dari penyedia mana pun (DigitalOcean, Vultr, Linode, atau provider lokal). Ubuntu 24.04 menyediakan PHP 8.3 langsung dari repositori bawaan, jadi tidak perlu menambah PPA untuk Laravel 13. Di Ubuntu 22.04 PHP bawaannya masih 8.1, sehingga butuh PPA
ppa:ondrej/php. - Domain dengan DNS A record menuju IP VPS. Opsional, tapi disarankan supaya SSL dan
APP_URLlebih rapi; lewat IP pun tetap jalan. - Istilah yang akan sering muncul: LEMP (Linux + Nginx + MySQL + PHP), PHP-FPM (proses manager PHP — Nginx tidak mengeksekusi PHP, ia meneruskan request lewat socket FastCGI), Composer, queue worker, GitHub Actions, dan deploy key (SSH key khusus untuk membaca repository).
Dua jalur yang akan dibahas
Jalur manual adalah pijakan pemahaman: kalian SSH ke server, git pull, lalu menjalankan daftar perintah post-deploy dengan urutan yang benar. Sederhana, tanpa biaya CI, tapi bergantung pada disiplin orang yang menjalankannya.
Jalur kedua memakai GitHub Actions: setiap push ke main memicu pipeline yang menginstall dependency, membangun aset, menjalankan tes, lalu mengirim aset hasil build dan menjalankan script deploy yang sama seperti jalur manual di VPS lewat SSH. Ini jalur yang direkomendasikan untuk rilis rutin.
Menyiapkan Server (Ubuntu 24.04 + LEMP)
Ubuntu 24.04 menyediakan Nginx 1.24 dan PHP 8.3 dari repositori bawaan — keduanya persis yang dibutuhkan Laravel 13. Mulai dari SSH ke VPS sebagai root.
Buat user deploy (non-root)
Jangan menjalankan deploy sebagai root. Composer menolak (atau minimal memperingatkan) berjalan sebagai root, dan file Laravel harus bisa ditulis oleh user tempat PHP-FPM berjalan — default Ubuntu: www-data.
# 1) SSH ke server, buat user deploy + grup www-data + grup sudo
sudo adduser deploy
sudo usermod -aG www-data deploy
sudo usermod -aG sudo deploy
Bagian setup server ini tetap dijalankan sebagai root; pindah ke user deploy nanti di bagian Memindahkan Kode. deploy sekalian dimasukkan ke grup sudo karena beberapa perintah sesudahnya — reload Nginx, supervisorctl, chown di /var/www — tetap butuh hak admin.
Install Nginx, PHP 8.3-FPM, MySQL, dan Composer
# 2) Update & install stack
sudo apt update
sudo apt install -y nginx php8.3-fpm php8.3-cli php8.3-mysql \
php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-gd \
php8.3-intl php8.3-bcmath composer git unzip mysql-server supervisor
# catatan: ekstensi tambahan sesuaikan kebutuhan proyek; cek dengan
# composer check-platform-reqs
# 4) Verifikasi
php -v # >= 8.3
nginx -v
mysql --version
composer --version
Daftar ekstensi di atas adalah pola umum proyek Laravel; sesuaikan dengan composer.json masing-masing. composer check-platform-reqs memeriksa PHP dan ekstensi yang terpasang terhadap kebutuhan package yang terpasang.
(Opsional) Node.js untuk build aset di server
Kalau deploy kalian nantinya memakai GitHub Actions, server tidak perlu Node — aset dibangun di CI. Tapi untuk jalur manual 100%, install Node 22 lewat NodeSource:
# 3) (Opsional) Node.js via NodeSource untuk build aset di server
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
Firewall
# 5) Firewall (UFW) — jangan sampai SSH terkunci
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Izinkan OpenSSH sebelum mengaktifkan UFW. Kalau urutannya kebalik dan SSH belum diizinkan, kalian terkunci dari server.
Membuat Database
Masuk ke MySQL lalu buat database dan user khusus aplikasi:
sudo mysql
CREATE DATABASE laravel_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'PASSWORD_KUAT';
GRANT ALL PRIVILEGES ON laravel_app.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
Laravel juga mendukung PostgreSQL, MariaDB, SQLite, dan SQL Server — untuk PostgreSQL polanya serupa dengan DB_CONNECTION=pgsql. Artikel ini fokus MySQL karena paling umum di tutorial LEMP.
Memindahkan Kode dari GitHub ke VPS
GitHub tetap jadi source of truth; VPS hanya tempat menjalankan. Ada dua cara memberi server akses ke repository: HTTPS + Personal Access Token (PAT) atau SSH + deploy key. Untuk repository privat, cara praktisnya: clone sekali memakai PAT, lalu selanjutnya cukup git pull — token tidak perlu disimpan ulang.
Sebelum lanjut, pastikan sesi SSH sudah memakai user deploy, bukan root:
whoami # harus mencetak: deploy
# kalau masih root, pindah user dulu:
su - deploy # masukkan password user deploy
# alternatif: tutup SSH, lalu masuk lagi dengan ssh deploy@IP
Seluruh perintah di bagian ini dan bagian Post-Deploy dijalankan sebagai deploy. sudo dipakai hanya untuk perintah admin (membuat folder di /var/www, dan seterusnya) — dan karena deploy sudah masuk grup sudo, perintah itu tetap jalan.
Klon repository
# SSH-key untuk akses git (bukan untuk CI)
ssh-keygen -t ed25519 -a 200 -C "deploy@appku"
cat ~/.ssh/id_ed25519.pub
# → tambahkan ke GitHub: Settings > Deploy keys (read-only) atau SSH keys akun
# Clone
sudo mkdir -p /var/www
sudo chown -R $USER:www-data /var/www
cd /var/www
git clone git@github.com:username/repo.git appku
Deploy key di GitHub bersifat read-only per repository — cocok untuk server yang hanya perlu menarik kode. Kepemilikan /var/www memakai grup www-data supaya PHP-FPM tetap bisa membaca dan menulis di sana. $USER pada chown di atas adalah deploy — sesi sudah berpindah dari root — dan di sinilah keanggotaan grup sudo tadi dipakai.
Buat .env production
cd /var/www/appku
cp .env.example .env
Lalu isi .env sesuai server (contoh minimal):
APP_NAME=AppKu
APP_ENV=production
APP_KEY=base64:... # hasil key:generate
APP_DEBUG=false
APP_URL=https://appku.com
LOG_CHANNEL=stack
LOG_LEVEL=error
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_app
DB_USERNAME=laravel_user
DB_PASSWORD=PASSWORD_KUAT
QUEUE_CONNECTION=database
CACHE_STORE=database
SESSION_DRIVER=database
Dua hal yang tidak bisa ditawar: APP_DEBUG=false (kalau true, detail konfigurasi sensitif bocor ke pengguna saat error) dan APP_KEY yang valid — tanpanya sesi dan encryption gagal dengan pesan "No application encryption key has been specified". APP_KEY cukup digenerate sekali di server, dan perintahnya dijalankan di bagian Post-Deploy — setelah composer install. Sebelum itu jangan menjalankan artisan apa pun: pada clone baru vendor/ belum ada, sedangkan artisan mem-bootstrap dari vendor/autoload.php, jadi perintahnya akan fatal error. .env tidak boleh masuk git; file .gitignore bawaan Laravel sudah menanganinya.
Post-Deploy: Perintah Artisan yang Wajib Dijalankan
Setiap kali kode baru masuk ke /var/www/appku, jalankan rangkaian ini:
composer install --no-dev --no-interaction --optimize-autoloader
php artisan key:generate # deploy pertama; .env sudah disiapkan sebelumnya
npm ci && npm run build # bila build aset di server (jalur manual)
php artisan migrate --force # atau --isolated untuk multi-server
php artisan storage:link --force # idempoten; aman dijalankan tiap rilis
php artisan optimize # = config:cache + event:cache + route:cache + view:cache
chmod -R 775 storage bootstrap/cache # bila perlu; untuk user non-www-data
Kenapa begitu banyak:
composer install --no-dev --no-interaction --optimize-autoloadermenginstall dependency persis sesuaicomposer.lock, tanpa packagerequire-dev(PHPUnit dsb. tidak perlu di production), sekaligus membangun classmap autoloader yang lebih cepat. Di tutorial lama sering muncul flag--prefer-dist; di Composer 2 mengunduh dist sudah jadi default sehingga flag itu tidak diperlukan lagi. Jalankan sebagai userdeploy, bukan root.php artisan key:generatemengisiAPP_KEYdi.env. Letaknya di urutan ini — setelahcomposer install— karena artisan mem-bootstrap darivendor/autoload.php; pada clone baruvendor/belum ada. Cukup dijalankan sekali di server;--forcehanya kalau memang sengaja regenerate.npm ci && npm run buildmenghasilkan aset Vite ber-hash dipublic/build.npm cidipilih daripadanpm installkarena memasang dependency persis daripackage-lock.json. Baris ini hanya untuk jalur manual — lewat GitHub Actions, aset dibangun di CI lalu dikirim ke server oleh step transfer.php artisan migrate --force— di production, migrasi menolak jalan tanpa konfirmasi, jadi--forcewajib. Untuk setup multi-server, tambahkan--isolatedagar dua server tidak migrasi bersamaan.php artisan storage:linkmembuat symlinkpublic/storage→storage/app/publicuntuk file pada diskpublic(upload gambar dsb.). Idempoten, apalagi dengan--force.php artisan optimizeadalah gabunganconfig:cache,event:cache,route:cache, danview:cachedalam satu perintah; jalankan ulang setiap deploy.chmod -R 775 storage bootstrap/cachehanya bila muncul error permission; pola umumnyachown -R deploy:www-data storage bootstrap/cache && chmod -R 775 storage bootstrap/cacheagar user deploy dan PHP-FPM sama-sama bisa menulis.
Untuk deploy berisiko (migration dengan perubahan besar), opsional: php artisan down --secret=xyz sebelum deploy dan php artisan up sesudahnya, supaya pengguna tidak melihat aplikasi setengah jadi.
Pentingnya urutan dan cache
Urutan punya konsekuensi nyata. Setelah config:cache dijalankan, file .env tidak lagi dibaca dan pemanggilan env() di luar file config akan mengembalikan null. Artinya: edit .env dulu sampai final, baru jalankan cache ulang — bukan sebaliknya. Praktik kedua yang harus ditegakkan: hanya pakai env() di dalam file config, jangan di controller atau view.
Catatan kedua: route:cache tidak mendukung route berbasis closure. Route harus memakai controller atau first-class callable. Kalau route:cache error, jalankan granular tanpa route cache dulu (php artisan config:cache && php artisan view:cache && php artisan event:cache), lalu perbaiki route tersebut dan kembali ke php artisan optimize di rilis berikutnya.
Verifikasi cepat setelah deploy: Laravel punya health check bawaan di /up — curl -I http://appku.com/up harus mengembalikan HTTP 200 (500 berarti aplikasi bermasalah). Ganti ke https:// setelah SSL terpasang di bagian berikutnya.
Konfigurasi Nginx (Virtual Host)
Virtual host Nginx untuk Laravel berikut diambil dari dokumentasi resmi Laravel, disesuaikan root dan domain. Simpan sebagai /etc/nginx/sites-available/appku (butuh hak admin — sebagai deploy, tulis lewat sudo nano /etc/nginx/sites-available/appku):
server {
listen 80;
listen [::]:80;
server_name appku.com www.appku.com;
root /var/www/appku/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 32k;
fastcgi_busy_buffers_size 64k;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Empat poin yang perlu dipahami, bukan sekadar disalin:
- Root menunjuk ke
public/, karenaindex.phpada di sana. Jangan pernah memindahkanindex.phpke root proyek — file.envdan konfigurasi lain ikut terekspos ke publik. fastcgi_passke socket PHP-FPM. Nginx hanya meneruskan request PHP ke proses PHP-FPM; di Ubuntu socket-nya/var/run/php/php8.3-fpm.sock(di banyak distro/var/runadalah symlink ke/run, jadi/run/php/php8.3-fpm.sockjuga valid).SCRIPT_FILENAMEmemakai$realpath_rootagar path tetap benar walau direktori deploy memakai symlink rilis.- Blok
deny alluntuk dotfile memblokir akses.env,.git, dan file tersembunyi lain kecuali/.well-known(dibutuhkan Certbot).
Aktifkan site-nya:
sudo ln -s /etc/nginx/sites-available/appku /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx -t mengevalidasi konfigurasi sebelum reload — kalau ada typo, Nginx menolak dan server lama tetap jalan. Pastikan site default di sites-enabled tidak menabrak konfigurasi ini. Verifikasi: curl -I http://IP-VPS/up harus 200.
SSL (opsional singkat)
SSL dengan Let's Encrypt bisa dipasang lewat Certbot:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d appku.com -d www.appku.com
Setelah itu APP_URL di .env seharusnya sudah https://appku.com. Detail lain ada di certbot.eff.org.
Menjaga Queue Worker Tetap Hidup (Supervisor)
php artisan queue:work adalah proses panjang: dia bisa mati karena timeout, restart server, atau error, dan — yang sering terlupa — dia tidak melihat kode baru sampai di-restart. Karena itu worker harus dikuasai process monitor seperti Supervisor.
Install Supervisor sudah termasuk di blok apt install sebelumnya. Buat file /etc/supervisor/conf.d/laravel-worker.conf (dengan sudo, misalnya sudo nano /etc/supervisor/conf.d/laravel-worker.conf):
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/appku/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=deploy
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/appku/storage/logs/worker.log
stopwaitsecs=3600
Teruskan konfigurasi ke Supervisor:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"
sudo supervisorctl status
Direktif yang perlu diperhatikan: numprocs menentukan berapa worker parallel (sesuaikan beban), user=deploy memastikan worker jalan sebagai user yang benar, stopwaitsecs=3600 harus lebih besar dari durasi job terpanjang (kalau tidak, worker dibunuh sebelum job selesai), dan stopasgroup/killasgroup memastikan seluruh proses grup ikut berhenti, bukan hanya proses induknya.
Saat deploy, jalankan php artisan queue:restart. Perintah ini membuat worker berhenti secara graceful — job yang sedang diproses tetap selesai, tidak ada yang hilang — lalu Supervisor menyalakan worker baru dengan kode terbaru. Sinyal restart disimpan lewat cache, jadi pastikan CACHE_STORE terisi dengan driver yang valid (database, file, atau redis). Mulai Laravel 12 ada juga php artisan reload yang sekaligus memicu queue:restart dan schedule:interrupt.
Alternatif systemd
Untuk yang tidak mau Supervisor, systemd bisa memegang worker dengan pola umum berikut (ini konvensi komunitas, bukan anjuran resmi Laravel). Simpan sebagai /etc/systemd/system/laravel-worker.service:
[Unit]
Description=Laravel Queue Worker
After=network.target
[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/appku
ExecStart=/usr/bin/php /var/www/appku/artisan queue:work --sleep=3 --tries=3
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now laravel-worker
# saat deploy: php artisan queue:restart (systemd Restart=always menyalakan ulang)
Metode Manual vs Otomatis (Perbandingan Singkat)
Jalur manual punya nilai jelas: kalian tahu persis apa yang dijalankan di server, tanpa dependensi layanan pihak ketiga. Kekurangannya terlihat di rilis ke sepuluh: ada sembilan langkah post-deploy yang harus diingat manusia, tidak ada tes yang dijalankan, dan build aset bisa berbeda antara lokal dan server kalau tidak hati-hati.
GitHub Actions menutup celah-celah itu. Test berjalan sebelum kode menyentuh server, aset dibangun di lingkungan CI yang bersih lalu dikirim ke server (Node tidak perlu dipasang di VPS), dan urutan deploy dijamin identik tiap kali. Konfigurasi awalnya memang lebih repot — sekali saja.
Untuk rilis rutin, pindahlah ke Actions. Untuk belajar atau server sekunder, jalur manual tetap berguna. Keduanya sama-sama menjalankan deploy Laravel tanpa Forge.
Automasi Deploy Laravel dari GitHub ke VPS dengan GitHub Actions
Bagian ini memandu jalur deploy Laravel dengan GitHub Actions dari nol: SSH key, secrets, lalu satu file workflow.
Siapkan SSH key untuk CI
Buat key khusus (bukan key pribadi yang dipakai sehari-hari) dan pasang public key-nya ke user deploy:
ssh-keygen -t ed25519 -a 200 -C "github-actions"
cat ~/.ssh/id_ed25519.pub | ssh deploy@IP 'cat >> ~/.ssh/authorized_keys'
# permission: ~/.ssh 700, authorized_keys 600
# lalu paste isi id_ed25519 (private) ke secret VPS_SSH_KEY
Buat GitHub Secrets
Buka Settings → Secrets and variables → Actions → New repository secret (atau via CLI: gh secret set NAMA). Tiga secret yang dibutuhkan:
VPS_HOST # IP atau domain server, mis. 203.0.113.10
VPS_USER # mis. deploy
VPS_SSH_KEY # isi file private key (-----BEGIN OPENSSH PRIVATE KEY----- ... )
Jangan pernah menaruh IP atau private key langsung di file workflow — di sana kode terbuka siapa pun untuk repository publik. Secrets hanya dibaca lewat ${{ secrets.NAMA }} saat workflow berjalan.
Workflow lengkap
Buat file .github/workflows/deploy.yml:
name: Deploy Laravel ke VPS
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout kode
uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, intl, bcmath, zip
coverage: none
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 22
cache: 'npm'
- name: Install dependency PHP
run: composer install --no-interaction --optimize-autoloader
- name: Siapkan .env untuk build & tes (jangan bocorkan secret!)
run: |
cp .env.example .env
php artisan key:generate
- name: Install dependency frontend
run: npm ci
- name: Build aset produksi
run: npm run build
- name: Jalankan test
run: php artisan test
# sesuaikan dengan setup test proyek (skeleton default: SQLite :memory:)
- name: Kirim aset build (public/build) ke VPS
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: 22
source: "public/build"
target: "/var/www/appku"
- name: Deploy lewat SSH ke VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: 22
script: |
set -e
cd /var/www/appku
git pull origin main
composer install --no-dev --no-interaction --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan storage:link --force
php artisan queue:restart
Beberapa keputusan di workflow ini yang perlu dipahami:
- CI memakai
composer installtanpa--no-dev, karenaphp artisan testbutuh packagerequire-dev(PHPUnit). Di server baru--no-devdipakai — production tidak butuh test runner. - Aset dibangun di CI lalu dikirim ke server dengan
appleboy/scp-action@v1. Kenapa perlu step transfer ini?public/buildmasuk.gitignore, jadigit pulldi server tidak akan pernah membawanya — tanpa transfer, halaman langsung errorVite manifest not found. Dengan pola ini server tetap tidak perlu Node, dan build selalu konsisten dengan kode yang dites. set -edi awal script server menggantikan opsiscript_stopyang sudah dihapus dariappleboy/ssh-action— kalau satu perintah gagal, sisa script berhenti sehingga deploy setengah jalan tidak diteruskan diam-diam.- Script server pada dasarnya adalah checklist manual dari bagian sebelumnya, dengan cache dieksplisitkan per perintah dan
storage:link --forcesupaya idempoten. Step test bisa dihapus kalau proyek butuh service tambahan yang tidak tersedia di runner.
Memicu deploy dan melihat hasilnya
Push ke main, lalu buka tab Actions di repository untuk memantau run-nya. Setiap push ke branch itu memicu pipeline yang sama: checkout → setup → install → build → test → transfer aset → SSH deploy. Push ke branch lain tidak memicu apa-apa, sesuai konfigurasi on.push.branches.
(Opsional) Envoy sebagai alternatif otomatisasi sederhana
Kalau GitHub Actions terasa berat dan kalian cukup butuh "jalankan daftar task di server remote", ada Laravel Envoy — definisinya ditulis dalam Blade:
composer require laravel/envoy --dev
@servers(['web' => ['deploy@203.0.113.10']])
@story('deploy')
pull-kode
install-dependency
jalankan-artisan
@endstory
@task('pull-kode', ['on' => 'web'])
cd /var/www/appku
git pull origin main
@endtask
@task('install-dependency', ['on' => 'web'])
cd /var/www/appku
composer install --no-dev --no-interaction --optimize-autoloader
@endtask
@task('jalankan-artisan', ['on' => 'web'])
cd /var/www/appku
php artisan migrate --force
php artisan optimize
php artisan queue:restart
@endtask
php vendor/bin/envoy run deploy
Envoy mendukung variabel CLI (--branch=...) dan hooks @before/@after/@error. Di Windows, Envoy butuh WSL2.
Masalah yang Sering Terjadi (dan Cara Mengatasinya)
- PHP terlalu tua di server —
composer installgagal dengan pesan "requires php ^8.3". Solusi: pakai Ubuntu 24.04 (PHP 8.3 bawaan) atau tambah PPAppa:ondrej/php. - "No application encryption key has been specified" — halaman error saat akses pertama. Jalankan
php artisan key:generatesetelahcomposer install(artisan butuhvendor/), dengan.envsudah dibuat. - Halaman putih / HTTP 500 tanpa pesan — sementara setel
APP_DEBUG=true, lalu cari exception distorage/logs/laravel.log; setelah diperbaiki, kembalikanAPP_DEBUG=false. Permission deniedsaat menulis storage/log — Laravel tidak punya izin tulis kestorage/bootstrap/cache. Jalankansudo chown -R deploy:www-data storage bootstrap/cache && sudo chmod -R 775 storage bootstrap/cache.Vite manifest not found/ aset 404 — CSS/JS tidak keluar karena hasil build tidak ada di server. Pastikan step build dan transferpublic/buildberjalan di CI, atau jalankannpm ci && npm run builddi server untuk jalur manual.env()mengembalikannullsetelahconfig:cache— urutan salah. Perbaiki.env, laluphp artisan config:cacheulang; ingat hanya pakaienv()di file config.route:cacheerror ("Unable to prepare route ... for serialization") — route memakai closure. Ganti ke controller atau first-class callable, atau jalankan cache lain dulu tanparoute:cache.- Worker masih menjalankan kode lama — job hasilnya tidak sesuai versi baru.
php artisan queue:restartdan pastikan Supervisor punyaautorestart=true. composer installhabis memory — fatal error out of memory. JalankanCOMPOSER_MEMORY_LIMIT=-1 composer install ....- Deploy crash di tengah — kode baru sudah masuk tapi database belum dimigrasi. Untuk deploy berisiko, gunakan maintenance mode opsional:
php artisan down→ deploy →php artisan up; dan selalumigrate --forcesetelah pull.
Dua kebiasaan pencegahan yang layak dibiasakan sejak awal: jangan commit .env (pastikan .gitignore memuatnya — kalau sudah terlanjur, rotasi semua kredensial yang bocor), dan jangan menjalankan deploy sebagai root.
Kesimpulan dan Langkah Berikutnya
Rantainya begini: server siap (LEMP + user deploy + firewall) → database dibuat → kode ditarik dari GitHub beserta .env produksi → perintah post-deploy dijalankan dengan urutan benar → Nginx menunjuk ke public/ → queue worker dipegang Supervisor → seluruhnya diulang otomatis oleh GitHub Actions di setiap rilis.
Saran pendekatannya sederhana: jalani jalur manual sekali atau dua kali di VPS baru supaya paham apa yang sebenarnya terjadi di setiap rilis, lalu pindah ke GitHub Actions untuk rilis berikutnya. Metode manual tetap berguna sebagai fallback.
Langkah lanjutan yang biasanya menyusul: pantau health route /up dengan uptime monitor, backup database terjadwal (mysqldump) plus rencana rollback (migrate:rollback --step=1 --force bila perlu), pasang SSL permanen dengan HSTS, dan pertimbangkan ASSET_URL kalau aset mulai diserve lewat CDN. Kalau mengelola server sendiri mulai terasa memberatkan, Laravel Forge dan Laravel Cloud adalah alternatif terkelola dari tim Laravel.
Pernah gagal deploy Laravel ke VPS? Ceritakan gejala dan solusinya di kolom komentar — bisa jadi itu persis yang dicari pembaca berikutnya.
Fitur komentar belum diaktifkan oleh administrator.