Back to blog
Laravel
Intermediate

Laravel Octane: High-Performance Application Scaffolding

Discover how Laravel Octane revolutionizes PHP application performance by leveraging persistent application servers. This comprehensive guide covers scaffolding, configuration, real-world examples, and production deployment techniques to achieve sub-100ms response times.

May 20, 2024

Introduction

PHP has long been criticized for its request-response lifecycle overhead, where each HTTP request triggers a full framework bootstrap, leading to significant latency in high-traffic applications. Laravel Octane fundamentally changes this paradigm by leveraging persistent application servers like Swoole and RoadRunner to keep your Laravel application in memory across requests. This eliminates the bootstrap overhead, reduces memory allocation, and can deliver performance improvements of 2x to 10x or more for typical web workloads.

This guide provides a complete, production-ready approach to scaffolding a Laravel Octane application from scratch. We'll cover installation, configuration, real-world optimization patterns, deployment strategies, and critical considerations for maintaining stability and security in high-throughput environments. Whether you're building APIs, real-time applications, or traditional web platforms, mastering Octane scaffolding is essential for modern PHP performance engineering.

Table of Contents

Core Concepts

Before diving into implementation, understanding the foundational principles of Laravel Octane is crucial for effective scaffolding and long-term maintenance.

Persistent Application State

Traditional PHP applications destroy and rebuild the entire application state with every HTTP request. This includes loading Composer autoloaders, booting service providers, registering facades, and initializing configuration. Octane changes this by running a persistent server process that keeps the Laravel application bootstrapped in memory. Subsequent requests reuse this initialized state, skipping the expensive bootstrap phase entirely.

Swoole and RoadRunner Engines

Octane supports two primary server engines:

  • Swoole: A PHP extension providing coroutine-based concurrency, HTTP/WebSocket servers, and asynchronous I/O capabilities. It offers the highest performance potential but requires installing a PECL extension.
  • RoadRunner: A Golang-based application server that communicates with PHP via RPC. It provides excellent performance without requiring PHP extensions, making it easier to deploy in restricted environments.

Both engines manage worker processes that handle incoming requests, significantly reducing latency by eliminating framework bootstrap time and enabling shared in-memory state (with important caveats about state management).

Statefulness and Reset Mechanisms

The persistent nature of Octane introduces statefulness challenges. Variables, singletons, and cached data persist across requests, which can cause memory leaks or unexpected behavior if not managed properly. Octane addresses this through:

  • Request Reset: Automatically resetting certain Laravel components (like the request, response, and session) between requests.
  • Manual Reset Hooks: Allowing developers to reset custom state via the Octane::resetting event.
  • Stateless Design Principles: Encouraging avoidance of static properties and global state that accumulates across requests.

Architecture Overview

Understanding Octane's internal architecture helps inform scaffolding decisions and troubleshooting strategies.

Request Flow Comparison

In a traditional PHP-FPM setup:

  1. Web server (Nginx/Apache) receives HTTP request
  2. Forwards to PHP-FPM pool
  3. PHP-FPM spawns/uses a PHP process
  4. PHP process boots Laravel from scratch
  5. Processes request and returns response
  6. PHP process may be recycled or kept alive (but Laravel state is not preserved)

With Laravel Octane (Swoole example):

  1. Web server receives HTTP request
  2. Forwards to Swoole server (embedded in Octane)
  3. Swoole worker (already booted Laravel) processes request
  4. Octane resets request-specific state
  5. Returns response
  6. Worker remains alive for next request

Worker Process Model

Octane uses a prefork worker model where:

  • A master process manages worker processes
  • Each worker contains a full Laravel application instance in memory
  • Workers handle requests concurrently (Swoole) or via Golang concurrency (RoadRunner)
  • Workers are periodically recycled to prevent memory accumulation
  • Maximum requests per worker can be configured to balance performance and memory safety

This architecture eliminates the per-request filesystem opcache hits and Symfony component initialization that typically consume 50-200ms in traditional Laravel requests.

Step-by-Step Guide

Let's scaffold a production-ready Laravel Octane application step by step, covering both Swoole and RoadRunner options.

Prerequisites

  • PHP 8.1 or higher
  • Composer 2.0+
  • For Swoole: PECL swoole extension installed
  • For RoadRunner: No additional PHP extensions required

Step 1: Install Laravel Application

Start with a fresh Laravel installation:

composer create-project laravel/laravel octane-appcd octane-app

Step 2: Install Octane Package

Add the Laravel Octane package via Composer:

composer require laravel/octane --dev

Note: Installed as dev dependency since Octane is primarily for local development and staging; production deployment uses the same codebase.

Step 3: Install Octane

Run the Octane installation command to publish configuration and choose your server:

php artisan octane:install

This interactive prompt will ask you to select between Swoole and RoadRunner. For maximum performance, choose Swoole; for easier deployment, choose RoadRunner.

Step 4: Configure Server Selection

After installation, you can explicitly choose your server by publishing the configuration:

php artisan vendor:publish --tag=octane-config

Then edit config/octane.php to set your preferred server:

// config/octane.phpreturn [    // ...    'server' => env('OCTANE_SERVER', 'swoole'), // or 'roadrunner'    // ...];

Step 5: Swoole-Specific Setup (If Chosen)

If you selected Swoole, ensure the extension is installed:

# For Ubuntu/Debiansudo apt-get install php-swoole# Or via PECLpecl install swoole# Add extension=swoole.so to your php.ini# Verify installationphp -m | grep swoole

For RoadRunner, no additional PHP setup is needed as it runs as a separate Golang binary managed by Octane.

Step 6: Optimize Laravel Configuration

Octane works best with certain Laravel optimizations:

# Cache configuration and routesphp artisan config:cachephp artisan route:cache# View caching is optional but recommendedphp artisan view:cache# Disable debug mode for performance# Set APP_ENV=production in your .env

Step 7: Start the Octane Server

Launch your Octane-powered application:

php artisan octane:start --server=swoole --workers=8 --max-requests=1000

Key parameters:

  • --server: 'swoole' or 'roadrunner'
  • --workers: Number of worker processes (typically 2-4x CPU cores)
  • --max-requests: Requests per worker before recycling (prevents memory leaks)
  • --port: Port to listen on (default 8000)

Step 8: Configure Web Server (Nginx Example)

For production, place a reverse proxy in front of Octane:

server {    listen 80;    server_name example.com;    root /path/to/octane-app/public;    location / {        try_files $uri $uri/ /index.php?$query_string;    }    # Proxy to Octane server    location ~ \.php$ {        proxy_pass http://127.0.0.1:8000;        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;    }}

Note: With Octane, you typically proxy all requests to the Octane server since it handles PHP execution directly. The above configuration shows a hybrid approach; pure Octane deployment would proxy all requests.

Real-World Examples

Let's examine concrete scenarios where Octane provides substantial benefits.

Example 1: High-Traffic API Endpoint

Consider a JSON API endpoint that retrieves user data:

// app/Http/Controllers/UserController.phpnamespace App\Http\Controllers;use App\Models\User;use Illuminate\Http\JsonResponse;use Illuminate\Http\Request;class UserController extends Controller{    public function show(Request $request, int $id): JsonResponse    {        // With Octane, User model bootstrapping happens once        $user = User::findOrFail($id);        return response()->json([            'id' => $user->id,            'name' => $user->name,            'email' => $user->email,            'created_at' => $user->created_at->toIso8601String(),        ]);    }}

In traditional PHP-FPM, each request incurs:

  • ~80ms for Laravel bootstrap

  • ~20ms for database connection initialization

  • ~10ms for User model loading

  • ~5ms for actual query execution

  • Total: ~115ms

With Octane (after warmup):

  • ~0ms for Laravel bootstrap (already loaded)

  • ~0ms for database connection (pooled and reused)

  • ~0ms for User model (already loaded)

  • ~5ms for actual query execution

  • Total: ~5ms (23x improvement)

Example 2: Real-Time WebSocket Server

Octane excels at WebSocket applications due to its persistent nature:

// app/Providers/OctaneServiceProvider.phpnamespace App\Providers;use Illuminate\Support\ServiceProvider;use Laravel\Octane\Octane;class OctaneServiceProvider extends ServiceProvider{    public function boot()    {        Octane::listen('websocket:message', function ($server, $data) {            // Broadcast message to all connected clients            $server->push(json_encode([                'type' => 'chat-message',                'data' => $data,                'timestamp' => now()->toIso8601String(),            ]));        });    }}

With traditional PHP, implementing WebSockets requires complex workarounds like Ratchet with ReactPHP or relying on external services. Octane's integrated server makes real-time features straightforward to implement within your Laravel application.

Production Code Examples

Here are realistic, production-ready code patterns optimized for Laravel Octane.

Example 1: Safe Service Container Usage

Avoiding static property accumulation in services:

// app/Services/ReportService.phpnamespace App\Services;use Illuminate\Contracts\Cache\Repository;class ReportService{    // ❌ BAD: Static property accumulates across requests    // private static $cache = [];    // ✅ GOOD: Use dependency injection    protected Repository $cache;    public function __construct(Repository $cache)    {        $this->cache = $cache;    }    public function generateReport(int $userId): array    {        $cacheKey = "report_{$userId}";        // ✅ Safe: Cache is request-scoped via Laravel's cache system        return $this->cache->remember($cacheKey, 3600, function () use ($userId) {            return [                'user_id' => $userId,                'generated_at' => now()->toIso8601String(),                'data' => $this->calculateExpensiveData($userId),            ]);        });    }    private function calculateExpensiveData(int $userId): array    {        // Simulate expensive operation        sleep(1);        return ['metrics' => rand(1, 100)];    }}

Example 2: Event Listener with Proper Cleanup

Ensuring event listeners don't retain state:

// app/Listeners/ProcessPodcast.phpnamespace App\Listeners;use App\Events\PodcastProcessed;use Illuminate\Contracts\Queue\ShouldQueue;class ProcessPodcast implements ShouldQueue{    // ✅ GOOD: No static properties    public $connection = 'redis';    public $queue = 'podcasts';    public $retryAfter = 90;    public function handle(PodcastProcessed $event): void    {        // Process podcast - no state retention issues        $podcast = $event->podcast;                // Example: Generate waveforms, extract metadata        $waveform = $this->generateWaveform($podcast->file_path);        $metadata = $this->extractMetadata($podcast->file_path);                $podcast->update([            'waveform' => $waveform,            'duration' => $metadata['duration'],            'processed_at' => now(),        ]);    }    private function generateWaveform(string $filePath): string    {        // Implementation        return 'waveform_data';    }    private function extractMetadata(string $filePath): array    {        // Implementation        return ['duration' => 180];    }}

Example 3: Configuration Caching for Zero-Bootstrap Overhead

Maximizing Octane benefits through aggressive caching:

// bootstrap/app.php$app = new Illuminate\Foundation\Application(    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__));// ✅ ESSENTIAL for Octane: Load cached configurationif (file_exists($app->cachedConfigPath())) {    $app->instance('config', require $app->cachedConfigPath());}// Load other cached services if neededif (file_exists($app->cachedRoutesPath())) {    $app->instance('routes', require $app->cachedRoutesPath());}return $app;

Comparison Table: Octane vs. PHP-FPM

Understanding the trade-offs helps inform deployment decisions.

Aspect Laravel Octane (Swoole) Traditional PHP-FPM
Request Latency (Cold Start) ~5-15ms (after warmup) ~80-200ms (full bootstrap)
Request Latency (Warm) ~2-8ms ~80-200ms
Memory Usage per Worker ~150-300MB (shared Laravel instance) ~20-50MB (per-request, not shared)
Maximum Concurrent Requests Limited by worker count × concurrency (Swoole: 1000s per worker) Limited by PHP-FPM process count (typically 10-100)
Setup Complexity Moderate (requires server configuration, state awareness) Low (standard PHP deployment)
Memory Leak Risk Higher (requires careful state management) Lower (state destroyed per request)
Deployment Flexibility Requires process manager (systemd, supervisord) Works with standard PHP-FPM pools
Supported Features WebSockets, TCP servers, custom event loops HTTP only (via web server)
Ideal Use Case High-throughput APIs, real-time apps, microservices Traditional websites, low-traffic apps, shared hosting

Best Practices

Following these practices ensures stable, high-performance Octane deployments.

1. Implement Proper State Reset Mechanisms

Use Octane's reset events to clear accumulative state:

// app/Providers/OctaneServiceProvider.phpuse Laravel\Octane\Events\Resetting;public function boot(){    Resetting::listen(function () {        // Clear any accumulated static state        MyService::$expensiveCache = [];                // Reset singleton instances if needed        app()->forgetInstance('problematic-singleton');                // Clear custom caches        Cache::flush();    });}

2. Configure Appropriate Worker Recycling

Prevent memory leaks by recycling workers regularly:

# Conservative setting for applications with potential leaksphp artisan octane:start --max-requests=500# Aggressive setting for well-audited codebasesphp artisan octane:start --max-requests=5000# Monitor memory usage to tune this value

3. Use Database Connection Pooling

Leverage persistent database connections to avoid reconnection overhead: