Logo

~/saidqb cat wiki/stack/server/deploy-laravel.md

MDdeploy-laravel

commit by saidqb

Deploy Laravel (PHP-FPM + Nginx)

Tiap project dapat Linux user sendiri (/home/<appuser>/app), bukan numpuk di satu folder /var/www bersama — kalau server ini dipakai buat banyak project, satu project ke-compromise tidak otomatis bisa baca file project lain.

Contoh command di dokumen ini pakai Ubuntu/Debian (apt). Kalau server-nya distro RHEL-based (Rocky Linux/AlmaLinux/CentOS Stream, pakai dnf), lihat catatan padanan di tiap langkah yang berbeda — repo bawaan RHEL biasanya PHP-nya sudah usang, jadi butuh repo tambahan Remi (rpms.remirepo.net) buat dapat PHP versi baru.

0. Prasyarat sebelum deploy

  • Akses SSH ke server (user dengan hak sudo) — disiapkan penyedia server, bukan sesuatu yang di-install
  • Domain sudah diarahkan (DNS A record) ke IP server — diatur di panel DNS registrar, bukan di server
  • Nginx: sudo apt install nginx -y
  • PHP 8.3 + extension yang dibutuhkan: sudo apt install php8.3-fpm php8.3-mbstring php8.3-xml php8.3-mysql php8.3-curl -y
  • Composer — pilih salah satu cara install:
    • Lewat package manager (gampang, tapi versinya sering ketinggalan jauh dari rilis terbaru): sudo apt install composer -y
    • Lewat installer resmi composer.phar (selalu versi terbaru, direkomendasikan): php -r "copy('https://getcomposer.org/installer','composer-setup.php');" && php composer-setup.php --install-dir=/usr/local/bin --filename=composer && php -r "unlink('composer-setup.php');"
    • Cek hasil install (cara mana pun): composer -V
  • Git: sudo apt install git -y
  • Text editor di server buat edit .env/config, minimal nano atau vim — server diakses lewat SSH, tidak ada GUI editor: sudo apt install vim -y
  • Database (MySQL/PostgreSQL) sudah jalan & kredensialnya siap diisi ke .env: sudo apt install mysql-server -y

Rocky Linux/AlmaLinux (Remi) — padanan Nginx, PHP, dan Composer di atas:

bash
sudo dnf install epel-release -y
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm -y   # sesuaikan angka 9 dengan versi major RHEL/Rocky yang dipakai
sudo dnf module reset php -y
sudo dnf module enable php:remi-8.3 -y
sudo dnf install nginx php-fpm php-mbstring php-xml php-mysqlnd php-curl composer git vim mariadb-server -y
sudo systemctl enable --now nginx php-fpm mariadb

dnf module enable php:remi-8.3 yang menentukan versi PHP-nya (bukan php8.3-fpm seperti di apt) — paket-paketnya tetap bernama generik php-fpm, php-mbstring, dst begitu module Remi diaktifkan. epel-release wajib dipasang duluan karena remi-release bergantung ke beberapa paket dari EPEL.

1. Buat user khusus untuk app

bash
sudo adduser --disabled-password --gecos "" laravelapp

--disabled-password — user ini tidak bisa login pakai password (cuma diakses lewat sudo -iu laravelapp dari user SSH yang sudah masuk). --gecos "" skip pertanyaan nama/nomor telepon dsb yang tidak relevan buat service account. Home directory-nya otomatis jadi /home/laravelapp.

2. Ambil kode & siapkan environment (jalan sebagai laravelapp)

bash
sudo -iu laravelapp
git clone -b production --single-branch <url-repo-git> app   # clone hanya branch production, tanpa riwayat/branch lain
cd app
composer install --no-dev --optimize-autoloader
cp .env.example .env       # cuma sekali di awal, lalu isi manual sesuai server
php artisan key:generate
exit                       # kembali ke user SSH biasa

sudo -iu laravelapp masuk sebagai user itu dengan $HOME=/home/laravelapp, jadi git clone ... app otomatis jadi /home/laravelapp/app — seluruh isinya otomatis kepemilikan laravelapp, tidak perlu chown manual lagi di langkah manapun setelah ini. -b production --single-branch supaya server cuma punya branch production (bukan main + seluruh branch lain) — cocok karena Langkah 8 juga selalu git pull origin production.

3. Buat database & user MySQL

bash
sudo mysql -u root -p
sql
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password-yang-kuat';
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Isi DB_DATABASE=app_db, DB_USERNAME=app_user, DB_PASSWORD=password-yang-kuat (dan DB_HOST=127.0.0.1) di .env sesuai yang dibuat di sini — php artisan migrate di Langkah 6 gagal kalau ini belum ada.

4. PHP-FPM pool khusus untuk laravelapp

Pool PHP-FPM default jalan sebagai www-data dan dipakai bersama semua site di server — biar app ini benar-benar isolated (proses PHP-nya jalan sebagai laravelapp, bukan www-data yang sama dengan project lain), bikin pool sendiri:

/etc/php/8.3/fpm/pool.d/laravelapp.conf

ini
[laravelapp]
user = laravelapp
group = laravelapp
listen = /run/php/laravelapp.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

listen.owner/listen.group tetap www-data supaya Nginx (yang jalan sebagai www-data) boleh nulis ke socket-nya, walau proses PHP di baliknya jalan sebagai laravelapp.

bash
sudo systemctl restart php8.3-fpm

Rocky Linux/AlmaLinux (Remi): path pool-nya /etc/opt/remi/php83/php-fpm.d/laravelapp.conf (bukan /etc/php/8.3/...), dan Nginx di RHEL-based jalan sebagai user nginx — ganti listen.owner/listen.group jadi nginx. Restart service-nya juga beda nama:

bash
sudo systemctl restart php83-php-fpm

5. Izin baca untuk Nginx

storage/bootstrap/cache sudah otomatis writable — pool PHP-FPM di atas jalan sebagai laravelapp, pemilik aslinya, jadi tidak perlu chown ke www-data seperti di setup shared folder. Yang masih perlu diatur cuma akses baca Nginx ke asset statis di public/ (home directory Linux defaultnya tertutup dari user lain):

bash
sudo chmod o+rx /home/laravelapp
sudo chmod -R o+rX /home/laravelapp/app/public

6. Nginx + PHP-FPM

nginx
server {
    listen 80;
    server_name app.example.com;
    root /home/laravelapp/app/public;

    index index.php;

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/laravelapp.sock;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
bash
sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'   # buka port 80/443 di firewall — tanpa ini situs tidak bisa diakses dari luar meski Nginx sudah jalan benar, kalau ufw aktif (default di banyak VPS)

Rocky Linux/AlmaLinux (Remi): Nginx RHEL-based tidak baca sites-available/sites-enabled bawaan Debian — taruh langsung file konfigurasinya di /etc/nginx/conf.d/app.conf (tanpa symlink), dan firewall default-nya firewalld, bukan ufw:

bash
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload

SELinux juga aktif default di RHEL-based — kalau Nginx gagal proxy_pass/baca socket PHP-FPM padahal config sudah benar, biasanya SELinux yang blok, bukan permission Linux biasa: sudo setsebool -P httpd_can_network_connect 1 (untuk proxy) dan pastikan context file public/ masih httpd_sys_content_t (sudo restorecon -Rv /home/laravelapp/app/public).

7. Migrasi & cache config

bash
sudo -iu laravelapp
cd app
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
exit

8. Urutan tiap kali deploy update

bash
sudo -iu laravelapp
cd app
git pull origin production
composer install --no-dev --optimize-autoloader
php artisan config:clear
php artisan config:cache
php artisan migrate --force
php artisan queue:restart      # kalau pakai queue worker — lihat catatan TIL soal ini
exit

Urutan config:clear sebelum config:cache penting — kalau langsung config:cache tanpa clear dulu, kadang masih baca cache config lama (lihat catatan TIL bulan Juli soal .env yang tidak ke-apply gara-gara ini).

Opsional: HTTPS

bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d app.example.com

Rocky Linux/AlmaLinux (Remi): paket certbot-nya dari EPEL (sudah ditambahkan di prasyarat), nama plugin-nya sedikit beda:

bash
sudo dnf install certbot python3-certbot-nginx -y
sudo certbot --nginx -d app.example.com

Konfigurasi SSL-nya tidak perlu ditulis manual — plugin --nginx otomatis suntik langsung ke server block yang sudah dibuat di Langkah 6 (listen 443 ssl, path sertifikat, redirect HTTP→HTTPS), dan pasang auto-renewal sendiri (sertifikat Let's Encrypt berlaku 90 hari, diperpanjang otomatis sebelum expired). Hasilnya kira-kira begini:

nginx
server {
    listen 80;
    server_name app.example.com;
    root /home/laravelapp/app/public;

    index index.php;

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/laravelapp.sock;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparam.pem; # managed by Certbot
}

server {
    if ($host = app.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name app.example.com;
    return 404; # managed by Certbot
}

Kalau nanti edit config ini lagi, jangan hapus baris # managed by Certbot — dan tetap sudo nginx -t && sudo systemctl reload nginx tiap habis edit.

Kalau domain dipasang di belakang Cloudflare (proxy oranye), lihat catatan cloudflare.md — ada penyesuaian mode SSL/TLS & opsi sertifikat yang perlu diperhatikan.