Advanced setupsSet up server with Laravel Octane

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 cache service, and your site must use Redis for its cache/broadcast connection. Keep the cache (Redis) service in your docker-compose.yml; without it, configuration and source changes may not take effect until the app container is restarted manually.
  • A backup of your Docker config. You must back up the docker-compose.yml and 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:

  1. The application image — Octane uses the -octane image tag, e.g. foxsystem/metafox-fpm:8.3-octane. This image runs in hybrid mode under supervisord: it serves dynamic traffic with Laravel Octane on port 8000 while keeping PHP‑FPM on port 9000 available for the install/upgrade workflow. The -octane image enables Octane automatically — there is no extra environment variable to toggle.
  2. 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.bk

These .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.yml currently mounts:
cd /path/to/metafox
grep -nE "image:|vhost-backend-nginx" docker-compose.yml

You 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.conf

Paste 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 different client_max_body_size than the 5000M above, 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.yml

Replace its contents with the Octane configuration below. There are only two differences from the standard PHP‑FPM stack — both marked with a # OCTANE comment:

  1. the application image tag ends in -octane, and
  2. the backend service mounts the vhost-backend-nginx.octane.conf you 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 # OCTANE changes in your existing file: append -octane to the application image tag, and point the backend service’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-orphans

Docker 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 ps

All 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 optimize

2. 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-server

3. Check the application logs if anything looks off:

docker compose logs -f app

You 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 app replicas over the same bind mount. If you scale the app service, each replica must have its own application filesystem. Multiple replicas sharing the same ./backend bind 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 worker service 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 optimize

Restoring 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