Pada bagian sebelumnya, kita telah membahas dua fondasi penting: bagaimana Laravel Octane bekerja bersama FrankenPHP, serta tantangan arsitektur in-memory seperti stale data, memory leak, dan manajemen service yang benar. Sekarang saatnya masuk ke tahap yang paling sering ditanyakan: bagaimana cara benar-benar menjalankan FrankenPHP untuk aplikasi Laravel, lalu mendeploy-nya ke server.

Di artikel ini, kita akan mengurangi teori dan lebih banyak fokus ke langkah implementasi. Target akhirnya sederhana: Anda punya aplikasi Laravel yang berjalan di atas FrankenPHP, dikemas dengan Docker, lalu bisa di-upload dan dijalankan di server Linux dengan pola deployment yang cukup aman untuk production.

Contoh pada artikel ini menggunakan pendekatan Docker + FrankenPHP + Laravel Octane karena paling praktis untuk deployment modern, mudah direproduksi, dan meminimalkan perbedaan antara lingkungan lokal dan server.

1. Persiapan Aplikasi Laravel

Pastikan proyek Laravel Anda sudah memiliki Octane dan FrankenPHP. Jika belum, jalankan:

composer require laravel/octane
php artisan octane:install

Saat installer bertanya server yang digunakan, pilih frankenphp.

Setelah itu, cek file konfigurasi utama berikut:

  • config/octane.php
  • .env
  • bootstrap/app.php bila Anda punya penyesuaian middleware atau exception handling

Tambahkan beberapa environment variable yang umum dipakai di production:

APP_NAME="MyApp"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://domainanda.com

LOG_CHANNEL=stack
LOG_LEVEL=info

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=app_db
DB_USERNAME=app_user
DB_PASSWORD=secret

CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

Jika Anda memakai Octane di production, sebaiknya hindari menyimpan state request ke properti statis atau singleton yang tidak di-reset. Pada tahap deploy, bug semacam ini sering baru terlihat setelah server menerima trafik nyata.

2. Menjalankan FrankenPHP Secara Lokal Sebelum Deploy

Sebelum membungkus aplikasi ke Docker, pastikan aplikasi memang berjalan normal dengan FrankenPHP di lokal:

php artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000

Untuk development, gunakan mode watch:

php artisan octane:start --server=frankenphp --watch

Jika aplikasi gagal naik, periksa beberapa hal berikut:

  • Apakah file .env valid dan tidak ada variabel yang hilang.
  • Apakah koneksi database dan Redis bisa diakses.
  • Apakah ada service singleton yang menyimpan state request.
  • Apakah package pihak ketiga kompatibel dengan Octane.

Setelah lokal stabil, baru masuk ke packaging untuk server.

3. Struktur Deployment dengan Docker

Pendekatan yang paling praktis adalah membuat container terpisah untuk:

  • app: Laravel + FrankenPHP + Octane
  • queue: worker untuk job queue
  • scheduler: menjalankan scheduler Laravel
  • db: MySQL/PostgreSQL bila Anda ingin satu stack penuh
  • redis: cache, queue, session

Untuk production, sering kali database dikelola terpisah. Tetapi untuk memudahkan pembelajaran, kita buat contoh yang lengkap.

Contoh Dockerfile untuk FrankenPHP

Buat file Dockerfile di root proyek:

FROM dunglas/frankenphp:php8.3

RUN install-php-extensions \
    pdo_mysql \
    bcmath \
    pcntl \
    opcache \
    redis \
    intl \
    zip

WORKDIR /app

COPY . /app

RUN cp .env.example .env || true

RUN composer install --no-dev --optimize-autoloader --no-interaction
RUN php artisan config:cache || true
RUN php artisan route:cache || true
RUN php artisan view:cache || true

RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache

EXPOSE 8000

CMD ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=8000"]

Ada beberapa poin penting dari Dockerfile ini:

  • Menggunakan image resmi dunglas/frankenphp.
  • Menginstal extension PHP yang umum dibutuhkan Laravel.
  • Menjalankan composer install dengan --no-dev untuk production.
  • Melakukan cache konfigurasi agar bootstrap lebih ringan.
  • Menjalankan server dengan host 0.0.0.0 supaya bisa diakses dari luar container.

Jika aplikasi Anda menggunakan frontend Vite, Anda bisa menambahkan proses build asset di tahap image build. Misalnya dengan multi-stage build.

Contoh Dockerfile dengan Build Asset

FROM node:20 AS frontend
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM dunglas/frankenphp:php8.3
RUN install-php-extensions pdo_mysql bcmath pcntl opcache redis intl zip
WORKDIR /app
COPY . /app
COPY --from=frontend /app/public/build /app/public/build
RUN composer install --no-dev --optimize-autoloader --no-interaction
RUN php artisan config:cache || true
RUN php artisan route:cache || true
RUN php artisan view:cache || true
RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache
EXPOSE 8000
CMD ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=8000"]

4. Menambahkan docker-compose untuk Lingkungan Server

Selanjutnya buat file docker-compose.yml:

version: '3.9'
services:
  app:
    build: .
    container_name: laravel_app
    restart: unless-stopped
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - redis
      - db

  queue:
    build: .
    container_name: laravel_queue
    restart: unless-stopped
    command: php artisan queue:work --sleep=3 --tries=3 --timeout=90
    env_file:
      - .env
    depends_on:
      - redis
      - db

  scheduler:
    build: .
    container_name: laravel_scheduler
    restart: unless-stopped
    command: sh -c "while true; do php artisan schedule:run --verbose --no-interaction; sleep 60; done"
    env_file:
      - .env
    depends_on:
      - redis
      - db

  redis:
    image: redis:7-alpine
    container_name: laravel_redis
    restart: unless-stopped

  db:
    image: mysql:8.0
    container_name: laravel_db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: app_db
      MYSQL_USER: app_user
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: rootsecret
    ports:
      - "3306:3306"
    volumes:
      - dbdata:/var/lib/mysql

volumes:
  dbdata:

Dengan komposisi ini, Anda sudah punya stack yang cukup lengkap. Untuk menjalankannya:

docker compose up -d --build

Lalu jalankan migrasi:

docker compose exec app php artisan migrate --force

Jika Anda perlu membuat application key:

docker compose exec app php artisan key:generate

5. Konfigurasi Reverse Proxy di Server

Meskipun FrankenPHP sudah berbasis Caddy, dalam banyak deployment nyata Anda tetap bisa menaruhnya di belakang reverse proxy seperti Nginx atau Traefik, terutama jika satu server menjalankan banyak aplikasi.

Contoh konfigurasi Nginx sederhana:

server {
    listen 80;
    server_name domainanda.com www.domainanda.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Jika Anda mengaktifkan HTTPS dengan Certbot pada Nginx, maka Nginx menangani SSL, sementara FrankenPHP fokus menjalankan aplikasi.

Namun jika Anda ingin memanfaatkan kemampuan Caddy/FrankenPHP secara langsung, Anda bisa mengekspose port 80 dan 443 dari container lalu menggunakan konfigurasi Caddyfile. Pendekatan ini bagus untuk instalasi yang lebih minimalis, tetapi perlu perencanaan lebih hati-hati jika satu server memuat banyak layanan.

6. Langkah Deploy ke Server Linux

Misalkan Anda memakai VPS Ubuntu. Alur deploy yang umum adalah sebagai berikut.

1. Instal Docker dan Compose Plugin

sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

2. Upload Source Code ke Server

Anda bisa memakai git clone langsung di server:

cd /var/www
sudo git clone https://github.com/username/nama-repo.git myapp
cd myapp

Atau upload melalui CI/CD. Jika deployment masih manual, minimal gunakan branch yang stabil dan hindari edit langsung di server.

3. Siapkan File Environment

Buat file .env production:

cp .env.example .env
nano .env

Isi variabel penting seperti:

  • APP_ENV=production
  • APP_DEBUG=false
  • APP_URL=https://domainanda.com
  • kredensial database
  • konfigurasi Redis, mail, dan queue

4. Build dan Jalankan Container

docker compose up -d --build

5. Generate Key dan Migrasi

docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate --force

6. Optimasi Laravel

docker compose exec app php artisan optimize
php artisan event:cache

Jika menjalankan perintah kedua, sebaiknya jalankan juga melalui container:

docker compose exec app php artisan event:cache

7. Update Aplikasi Tanpa Banyak Downtime

Untuk deployment pembaruan kode, pola minimumnya:

git pull origin main
docker compose up -d --build
docker compose exec app php artisan migrate --force
docker compose exec app php artisan optimize

Untuk aplikasi dengan trafik lebih tinggi, Anda bisa mengarah ke strategi blue-green deployment atau rolling update lewat orchestrator seperti Docker Swarm atau Kubernetes. Namun untuk banyak aplikasi Laravel skala kecil sampai menengah, alur di atas sudah cukup layak jika waktu build image tidak terlalu lama.

Jika Anda ingin worker Octane di-restart setelah update konfigurasi atau kode:

docker compose restart app

Jangan lupa bahwa Octane menjalankan aplikasi secara persisten di memori. Jadi perubahan kode tidak akan aktif sampai proses di-restart atau container dibangun ulang.

8. File .dockerignore yang Sering Terlupa

Agar image lebih kecil dan build lebih cepat, buat file .dockerignore:

.git
node_modules
vendor
storage/logs
storage/framework/cache
storage/framework/sessions
storage/framework/views
.env
Dockerfile
docker-compose.yml

Ini penting agar context build tidak terlalu besar, terutama pada proyek dengan direktori node_modules yang berat.

9. Logging, Debugging, dan Masalah Umum

Melihat log container

docker compose logs -f app
docker compose logs -f queue

Masuk ke shell container

docker compose exec app sh

Masalah umum yang sering terjadi

  • Permission error pada storage atau bootstrap/cache. Solusinya pastikan owner dan permission benar saat image dibangun.
  • Perubahan kode tidak muncul. Ini normal pada Octane jika proses belum di-restart.
  • Queue tidak jalan. Pastikan service queue aktif dan QUEUE_CONNECTION sesuai.
  • Koneksi database gagal. Periksa host DB. Dalam Docker Compose, host biasanya nama service, misalnya db, bukan 127.0.0.1.
  • Aplikasi lambat setelah deploy. Cek apakah cache Laravel sudah dibuat, dan cek query lambat atau bottleneck eksternal seperti API pihak ketiga.

10. Kapan Perlu Supervisor?

Jika Anda tidak memakai Docker, maka Supervisor sangat berguna untuk menjaga proses FrankenPHP, queue worker, dan scheduler tetap hidup. Contoh konfigurasi Supervisor untuk Octane:

[program:laravel-octane]
process_name=%(program_name)s
command=php /var/www/myapp/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-octane.log

Lalu untuk queue worker:

[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/myapp/artisan queue:work --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-queue.log

Jika Anda sudah menggunakan Docker dengan restart policy yang baik, Supervisor di dalam container biasanya tidak diperlukan. Sebaiknya satu container menjalankan satu proses utama agar lifecycle-nya lebih mudah dikelola.

Kesimpulan

FrankenPHP sangat menarik untuk aplikasi Laravel modern karena proses setup-nya relatif sederhana, performanya baik, dan sangat cocok dipadukan dengan Laravel Octane. Namun, manfaat sebenarnya baru terasa ketika workflow deployment Anda juga rapi: image bisa dibangun ulang dengan konsisten, service queue dan scheduler dipisahkan, environment production jelas, dan proses restart aplikasi bisa diprediksi.

Di artikel ini, kita tidak hanya menjalankan FrankenPHP secara lokal, tetapi juga membawanya sampai ke server menggunakan Docker, lengkap dengan contoh Dockerfile, docker-compose.yml, reverse proxy, langkah upload ke VPS, dan debugging dasar. Untuk tahap berikutnya, Anda bisa melanjutkan dengan otomatisasi CI/CD, health check, monitoring, serta strategi deployment tanpa downtime yang lebih matang.

Jika fondasi ini sudah stabil, maka FrankenPHP bukan sekadar eksperimen performa, melainkan bisa menjadi bagian dari stack production Laravel Anda sehari-hari.