Set up server with Laravel Octane
Laravel Octane boots your MetaFox application once and keeps it resident in memory using a high‑performance application server. Instead of bootstrapping the framework on every request (as PHP‑FPM does), Octane serves requests from long‑lived workers, which can significantly improve response time and throughput on busy communities.
The MetaFox package delivered to customers ships with the standard PHP‑FPM Docker stack — it does not include an Octane Compose file or the matching Nginx configuration. This guide therefore walks you through enabling Octane in place: you will back up your current files, add the Octane Nginx configuration, and edit your existing docker-compose.yml directly.
Not comfortable doing this yourself? Enabling Octane requires solid Linux/Docker server skills and edits to your Compose and Nginx files. If you are not confident performing the steps below, please submit a ticket to our Technical Support team and we will assist you: https://clients.phpfox.com/submitticket.php?step=2&deptid=12
Before you begin
Please read this section carefully. This guide is only applicable when all of the following requirements are met.
Requirements
- Docker‑based installation only. This guide applies exclusively to MetaFox sites that were set up and run with Docker / Docker Compose (see Prepare for Installation with Docker). It does not apply to bare‑metal / manual installations that run PHP‑FPM directly on the host.
- MetaFox version
>= 5.2.4. The Octane image and reload mechanism are only supported from MetaFox 5.2.4 onward. You can verify your current version in AdminCP → Site status. Do not attempt this on older versions. - Linux server knowledge. You must be comfortable with Linux server administration: connecting over SSH with root/sudo privileges, using the terminal, editing files (e.g. with
nano/vi), and operating Docker and Docker Compose (docker compose ps,up,down,logs,exec). - Redis is required. Octane keeps the application resident in memory, so after an admin change or upgrade the long‑lived workers must be reloaded. MetaFox coordinates that reload through a Redis pub/sub control channel — so Octane mode requires Redis. The standard MetaFox Docker stack already ships Redis as the
cacheservice, and your site must use Redis for its cache/broadcast connection. Keep thecache(Redis) service in yourdocker-compose.yml; without it, configuration and source changes may not take effect until theappcontainer is restarted manually. - A backup of your Docker config. You must back up the
docker-compose.ymland Nginx files you are about to change before making any change (see Step 1).
Back up first — this is mandatory
This procedure edits your docker-compose.yml and adds a new Nginx configuration. While every change is reversible, always back up these files before you start so you can roll back if anything goes wrong. Backing up is covered in Step 1.
How Octane runs in MetaFox
Enabling Octane changes only two things in your Docker stack:
- The application image — Octane uses the
-octaneimage tag, e.g.foxsystem/metafox-fpm:8.3-octane. This image runs in hybrid mode undersupervisord: it serves dynamic traffic with Laravel Octane on port8000while keeping PHP‑FPM on port9000available for the install/upgrade workflow. The-octaneimage enables Octane automatically — there is no extra environment variable to toggle. - The Nginx virtual host — a dedicated Octane vhost sends normal traffic to the Octane upstream (
app:8000) and deliberately routes the install wizard and upgrade endpoints to PHP‑FPM (app:9000), because those steps must run before the long‑lived workers are warmed up.
Everything else — the database, Redis, Memcached, RabbitMQ queue, the queue worker and the scheduler — stays exactly the same. You are only changing the application server, not your data, settings, or environment configuration (docker/server.env).
In the steps below we assume your MetaFox package was extracted to /path/to/metafox. Replace that with your real path.
Step 1: Back up your system
Connect to your server over SSH and go to the folder where the MetaFox package was extracted.
Back up the Docker configuration files you are about to change. Copy docker-compose.yml to a .bk file before editing it (and back up your current Nginx vhost as well):
cd /path/to/metafox
cp docker-compose.yml docker-compose.yml.bk
cp docker/vhost-backend-nginx.conf docker/vhost-backend-nginx.conf.bkThese .bk files are what you will restore from if you need to roll back (see Reverting to PHP‑FPM).
Step 2: Confirm requirements
- Confirm your MetaFox version is
>= 5.2.4(AdminCP → Site status). If it is older, update MetaFox first (see Update MetaFox). - Confirm your existing stack is the standard Docker stack and note the application image tag and the Nginx vhost file your
docker-compose.ymlcurrently mounts:
cd /path/to/metafox
grep -nE "image:|vhost-backend-nginx" docker-compose.ymlYou should see an application image ending in your PHP tag (e.g. foxsystem/metafox-fpm:8.3) and the backend service mounting ./docker/vhost-backend-nginx.conf. You will change both in the next steps.
Step 3: Create the Octane Nginx configuration
The Octane Nginx vhost is not included in the delivered package, so create it now. Using a text editor, create a new file at docker/vhost-backend-nginx.octane.conf:
cd /path/to/metafox
nano docker/vhost-backend-nginx.octane.confPaste in the following content exactly, then save and exit:
# WebSocket upgrade map (http context — this file is included from nginx.conf).
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Backend upstreams. The `app` container runs hybrid mode under supervisord:
# Laravel Octane on :8000 + PHP-FPM on :9000.
upstream metafox_octane { server app:8000; }
upstream metafox_fpm { server app:9000; }
server {
listen 8080;
listen [::]:8080 ipv6only=on;
root /app/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.php index.html;
charset utf-8;
# ── INSTALL WIZARD: bypass Octane ──────────────────────────────────
# Static install SPA lives at /app/public/install/. Serve it directly
# from disk (no PHP involvement) with SPA fallback to index.html.
location ^~ /install/ {
try_files $uri $uri/ /install/index.html =404;
}
# Install wizard API and upgrade workflow MUST hit PHP-FPM so the
# bootstrap can intercept the request before the autoloader is loaded.
location ^~ /api/v1/install {
try_files $uri @fpm;
}
# The upgrade workflow must hit PHP-FPM. Anything else goes to Octane
# (admin-cp UI, status checks, etc.).
location ~ ^/admincp/app/upgrade {
try_files $uri @fpm;
}
location /storage {
root /app/public;
try_files $uri 404;
}
# ── RUNTIME: Laravel Octane ────────────────────────────────────────
location / {
if ($http_user_agent ~* "facebookexternalhit|telegrambot|whatsapp|twitterbot" ) {
rewrite /(.*) /sharing/$1?$query_string last;
}
try_files $uri $uri/ @octane;
}
location ^~ /sharing/ {
try_files $uri $uri/ @octane;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location @octane {
proxy_pass http://metafox_octane;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 300;
add_header X-Server "octane";
}
location @fpm {
fastcgi_pass metafox_fpm;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /app/public/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param DOCUMENT_ROOT /app/public;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_read_timeout 600;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
add_header X-Server "php-fpm";
}
location ~ /\.(?!well-known).* {
deny all;
}
client_max_body_size 5000M;
}If your current vhost (
docker/vhost-backend-nginx.conf) contains a differentclient_max_body_sizethan the5000Mabove, set the same value here so large uploads keep working.
Step 4: Update docker-compose.yml
Open your existing Compose file for editing (you already backed it up to docker-compose.yml.bk in Step 1):
cd /path/to/metafox
nano docker-compose.ymlReplace its contents with the Octane configuration below. There are only two differences from the standard PHP‑FPM stack — both marked with a # OCTANE comment:
- the application
imagetag ends in-octane, and - the
backendservice mounts thevhost-backend-nginx.octane.confyou created in Step 3.
Everything else is identical, and the ${...} placeholders continue to read from your existing docker/server.env — so your database, queue and other settings are untouched.
x-app-common: &app-common
# OCTANE: application image must use the "-octane" tag.
image: "${METAFOX_APP_IMAGE:-foxsystem/metafox-fpm:8.3-octane}"
restart: unless-stopped
volumes:
- ./backend:/app:rw
networks:
- metafox
depends_on:
database:
condition: service_healthy
cache:
condition: service_healthy
memcached:
condition: service_healthy
rabbitmq:
condition: service_healthy
services:
database:
image: "${POSTGRES_IMAGE:-postgres:13.2}"
restart: unless-stopped
environment:
POSTGRES_USER: "${MFOX_DAT_USR}"
POSTGRES_PASSWORD: "${MFOX_DAT_PW}"
POSTGRES_DB: "${MFOX_DAT_DBNAME}"
ports:
- "5556:5432"
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${MFOX_DAT_USR} -d ${MFOX_DAT_DBNAME}"]
interval: 5s
timeout: 3s
retries: 20
networks:
metafox:
aliases: [postgres]
cache:
image: "${CACHE_IMAGE:-redis:6.2.0}"
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes", "--protected-mode", "no"]
environment:
ALLOW_EMPTY_PASSWORD: "yes"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
networks:
metafox:
aliases: [redis]
memcached:
image: "${MEMCACHED_IMAGE:-memcached:1.6.21}"
restart: unless-stopped
ports:
- "11211:11211"
networks:
- metafox
healthcheck:
test: ["CMD-SHELL", "timeout 2 bash -c '</dev/tcp/127.0.0.1/11211'"]
interval: 5s
timeout: 3s
retries: 10
rabbitmq:
image: "${RABBITMQ_IMAGE:-rabbitmq:3}"
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: "${MFOX_QUEUE_USER:-guest}"
RABBITMQ_DEFAULT_PASS: "${MFOX_QUEUE_PASSWORD:-guest}"
ports:
- "5672:5672" # AMQP
- "15672:15672" # management UI
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 10
networks:
- metafox
backend:
image: nginx
restart: unless-stopped
volumes:
- ./backend:/app:ro
# OCTANE: mount the Octane Nginx vhost created in Step 3.
- ./docker/vhost-backend-nginx.octane.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- "8080:8080"
networks:
- metafox
app:
<<: *app-common
worker:
<<: *app-common
user: "daemon:daemon"
command: ["php", "/app/artisan", "queue:work", "--queue=default", "--tries=3", "--timeout=600"]
scheduler:
<<: *app-common
user: "daemon:daemon"
command: ["bash", "-c", "(php /app/artisan schedule:run) && (sleep 10)"]
networks:
metafox:
driver: bridge
volumes:
pg_data:
redis_data:
rabbitmq_data:Save and exit.
If you previously customized your
docker-compose.yml(extra ports, volumes, environment, etc.), do not paste over it blindly. Instead, make only the two# OCTANEchanges in your existing file: append-octaneto the applicationimagetag, and point thebackendservice’s vhost mount to./docker/vhost-backend-nginx.octane.conf.
Step 5: Recreate the stack
Apply the changes by recreating the containers. This does not delete your database or uploaded files — those live in named volumes and the bind‑mounted backend folder.
cd /path/to/metafox
docker compose down --remove-orphans
docker compose up -d --remove-orphansDocker will pull the -octane application image on first run, which may take a few minutes. Once it completes, check that every service is up and healthy:
docker compose psAll services should report a running/healthy state.
Step 6: Warm up and verify
1. Rebuild caches and reload the Octane workers. Under Octane, source/config changes are not auto‑reloaded — they are picked up by running optimize, which also reloads the long‑lived workers:
docker compose exec -u daemon app php artisan optimize2. Verify the site loads and is served by Octane. Open your site in a browser:
http://your_server_ip:8080/You can confirm Octane is handling requests by checking the X-Server response header — it returns octane for normal pages and php-fpm for the install/upgrade endpoints:
curl -sI http://your_server_ip:8080/ | grep -i x-server3. Check the application logs if anything looks off:
docker compose logs -f appYou should see the Octane server start up. The install wizard and upgrade endpoints are intentionally still served by PHP‑FPM inside the same container — this is expected behaviour.
Important caveats when running Octane
Because Octane keeps the application in memory, a few operational rules differ from PHP‑FPM:
- Reload after admin changes / upgrades. Changes that affect compiled config, routes or source code (admin settings that rebuild config, app install/uninstall, or running an upgrade) require the Octane workers to reload to take effect. MetaFox handles this automatically by broadcasting a reload over Redis pub/sub (which is why Redis is required — see Requirements). If a change does not appear immediately, run:
docker compose exec -u daemon app php artisan optimize - Install & upgrade still go through PHP‑FPM. The Octane Nginx vhost routes the install wizard (
/install/,/api/v1/install) and the upgrade workflow to PHP‑FPM on purpose. You do not need to switch back to FPM to run an upgrade. - Do not run multiple
appreplicas over the same bind mount. If you scale theappservice, each replica must have its own application filesystem. Multiple replicas sharing the same./backendbind mount will overwrite each other’s Octane server‑state file and behave unpredictably. - Queue must be RabbitMQ. Keep the queue backend on RabbitMQ so the
workerservice drains jobs.
Reverting to PHP‑FPM
If you need to roll back, restore the Compose file you backed up in Step 1 and recreate the stack. Your data and settings are unchanged:
cd /path/to/metafox
docker compose down --remove-orphans
cp docker-compose.yml.bk docker-compose.yml
docker compose up -d --remove-orphans
docker compose exec -u daemon app php artisan optimizeRestoring docker-compose.yml.bk reverts both the image tag and the Nginx vhost back to the PHP‑FPM configuration. The docker/vhost-backend-nginx.octane.conf file can be left in place; it is simply no longer referenced.
Need help?
If you run into trouble at any step, or you would prefer our team to perform the migration for you, please submit a support ticket and include your server details and the output of docker compose ps and docker compose logs app:
https://clients.phpfox.com/submitticket.php?step=2&deptid=12