PHP compatibility and project lifecycle
Supported PHP versions, Laravel versions, package release status and licence are not stated in the supplied source packet and are unknown. Confirm PHP 8.4 support in the official documentation before use. PHP 8.5 is planned for the track but unverified here.
Official sources checked 2026-10-22. Browser lessons on this site run PHP 8.5.
What this project is
Laravel Octane is a Laravel package, not a PHP interpreter and not a standalone runtime. Its documentation describes it as serving a Laravel application through an application server. The servers named are FrankenPHP, Open Swoole, Swoole and RoadRunner. Octane boots the application once, keeps it in memory, and feeds it requests. The classification here is framework runtime integration: the framework adapts itself to a long-lived server process, and the PHP language runtime underneath is still ordinary PHP.
Three distinctions help avoid confusion:
- PHP core is the language engine. Octane is Laravel-specific code that sits above it. Laravel is a framework, not a CMS, so nothing here concerns Drupal or any other CMS product.
- Swoole and Open Swoole are separate PHP extensions, installed with separate PECL package names. The documentation says Open Swoole grants the same functionality as Swoole, such as concurrent tasks, ticks and intervals.
- FrankenPHP and RoadRunner are Go-based application servers. They are not PHP versions or forks.
Architecture and execution model
In a classic request lifetime, each request starts from a clean slate. The application is bootstrapped, handles one request, and its state is discarded. Octane replaces this with persistent workers. The application is loaded when the server starts, and the same in-memory application serves many requests.
The documentation describes several consequences:
- Edits to files, including new routes in routes/web.php, are not picked up until the server restarts. A --watch flag can restart the server on file changes. It needs Node and the Chokidar library.
- By default, Octane starts one request worker per CPU core. The table of contents lists options for worker count, maximum request count, maximum execution time, reloading workers and stopping the server.
- Octane's own table of contents also lists sections on dependency injection, managing memory leaks, concurrent tasks, ticks and intervals, the Octane cache and tables. The supplied source packet is truncated before those sections, so their details are unknown here. Read them in the official documentation before relying on them.
State leakage and cleanup
With persistent workers, anything held in memory can outlive a single request. Static properties, singletons bound in the container, and objects that capture a request or configuration can carry data from one visitor to the next. The result can be stale data, cross-user data exposure, or memory growth. The documentation dedicates sections to dependency injection (container, request and configuration repository injection) and to managing memory leaks.
For your own code, a few habits follow from this model:
- Treat static properties and long-lived singletons as shared across requests.
- Do not store request-specific data in them. Prefer per-request objects.
- Be careful when injecting the container, the request or the configuration repository into long-lived objects. Verify the official guidance first.
- Use the worker request limit as a safety net, not as a substitute for fixing leaks.
The documentation's development Compose file runs with a max-requests value of 1, which restarts the worker after each request. That reduces leak and stale-code symptoms, so a leak that shows up under a persistent worker can be hidden in that setup. The example below deliberately omits that flag so you can see the effect.
Practical use cases
Octane suits Laravel applications where you want to avoid re-bootstrapping on every request, and where you can audit your code and packages for per-request state. It is a poor fit if you cannot review shared state, or if your hosting cannot run and supervise a long-lived process. Whether it improves speed for your workload is something to measure yourself. This guide makes no performance claims.
Installation approach
The documented path is to install the package with Composer and then run the octane:install Artisan command, which installs Octane's configuration file. You then choose a server:
- FrankenPHP: with Octane, the binary can be downloaded for you. Official FrankenPHP Docker images are also documented for development and production, and they add platform support such as Windows.
- RoadRunner: Octane offers to download the binary on first start. Under Sail, extra Composer packages and the rr executable are documented.
- Swoole or Open Swoole: install the matching PHP extension, typically with PECL.
Start the server with the octane:start Artisan command. By default it listens on port 8000. The server comes from the server option in the octane configuration file. A custom Caddyfile can be supplied for FrankenPHP with a documented option.
Strengths
- It integrates several documented server backends behind one Laravel command.
- It offers FrankenPHP, Swoole and RoadRunner paths, with Laravel Sail instructions for each.
- It provides built-in file watching for development.
- It offers a clear production pattern: a process monitor plus a reverse proxy.
Limitations and unknowns
- Supported PHP versions, Laravel version constraints, package release status and licence are not stated in the supplied source packet. They are unknown. The learning track uses PHP 8.4 with PHP 8.5 planned, so confirm compatibility before you try either.
- The packet is truncated, so the memory-leak guidance, concurrency features and cache or table details are unverified here.
- Persistent workers add real operational risk. Application and package code must be safe to run repeatedly in one process.
- Lifecycle is marked unknown because the supplied evidence does not state a support policy.
Deployment
The documentation recommends a process monitor such as Supervisor to keep Octane running. It also recommends serving behind a traditional web server such as Nginx or Apache. That web server can serve static assets and handle certificate termination, and it proxies other requests to the Octane server on port 8000. When serving over HTTPS, the OCTANE_HTTPS environment variable controls whether generated links use https://. Keep the Octane port bound to localhost behind the proxy rather than exposed publicly. Laravel Cloud is mentioned in the source as offering managed Octane support. This guide does not cover provisioning.
Examples in this guide
The first example assumes an existing Laravel application. It adds a Docker setup adapted from the documentation, plus a tiny route that makes a leaky static counter visible. The second example shows the documented Supervisor and Nginx pairing. Both need Composer, Docker or a server, and network access. They are not browser exercises.
Links
The only source used is the official Laravel Octane documentation page, linked below.
Practical examples
These examples have not been run in a live environment yet. Commands and versions follow the official documentation linked on this page; check them before relying on them, and report a problem if one does not work.
FrankenPHP via Docker Compose with a visible static-state leak
Adapted from the Dockerfile and Compose file in the Octane documentation. It assumes an existing Laravel project root. Replace routes/web.php with the file shown and add the helper class. The counter route shows that a static property survives across requests inside a persistent worker. The documented development file uses a max-requests value of 1, which would hide this behaviour, so this file omits it.
Dockerfile
FROM dunglas/frankenphp
RUN install-php-extensions \
pcntl
# Add other PHP extensions here...
COPY . /app
ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
compose.yaml
# compose.yaml
services:
frankenphp:
build:
context: .
entrypoint: php artisan octane:frankenphp --workers=1
ports:
- "127.0.0.1:8000:8000"
volumes:
- .:/app
app/Support/RequestCounter.php
declare(strict_types=1);
namespace App\Support;
/**
* Deliberately leaky: the static property lives as long as the worker process,
* not as long as a single request.
*/
final class RequestCounter
{
private static int $hits = 0;
public static function hit(): int
{
self::$hits++;
return self::$hits;
}
}
routes/web.php
use App\Support\RequestCounter;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return 'Octane example';
});
Route::get('/counter', function () {
return response()->json([
'static_counter' => RequestCounter::hit(),
'pid' => getmypid(),
]);
});
Install and run
composer require laravel/octane
php artisan octane:install --server=frankenphp
docker compose up --build
curl http://127.0.0.1:8000/counter
curl http://127.0.0.1:8000/counterExpected behavior
If the container starts and is reachable, the first /counter response should show static_counter 1 and the second should show 2 with the same pid. That would indicate the static value persisted across requests in one worker. With the classic per-request model, or with a worker limit of 1 request, it would reset to 1.
Requires an existing Laravel application, Composer, Docker and network access. The Compose file uses the untagged image name from the documentation, so pin it yourself. Whether the server listens on an address reachable through the published port inside the container is not confirmed by the source and needs an integration check. PHP 8.4 support is unconfirmed. Code changes need a container restart. Not a browser exercise.
Supervisor and Nginx in front of Octane on a native host
Reproduces the documented production pattern: Supervisor keeps octane:start running on 127.0.0.1:8000, and Nginx serves static files and proxies other requests to it. The paths, user and domain are placeholders from the documentation. Adjust them for your own server.
deploy/octane.conf
[program:octane]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/storage/logs/octane.log
stopwaitsecs=3600
deploy/nginx-octane.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name domain.com;
server_tokens off;
root /home/forge/domain.com/public;
index index.php;
charset utf-8;
location /index.php {
try_files /not_exists @octane;
}
location / {
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; }
access_log off;
error_log /var/log/nginx/domain.com-error.log error;
error_page 404 /index.php;
location @octane {
set $suffix "";
if ($uri = /index.php) {
set $suffix ?$query_string;
}
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 Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8000$suffix;
}
}
Install and run
php artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000Expected behavior
Octane listens on 127.0.0.1:8000 only. Nginx serves existing static files from the public directory and proxies other requests to Octane. Supervisor restarts the Octane process if it exits.
Placeholders for domain, user and paths must be replaced. The Supervisor and Nginx installation steps are not covered. Not a browser exercise. TLS setup is not shown. The OCTANE_HTTPS setting should be considered when serving over HTTPS.