runtime/laravel
Runtime for Laravel integrates the Runtime PHP application server with Laravel, giving you a simple way to serve requests, run workers, and deploy apps with minimal configuration. It handles the glue code so you can focus on building your Laravel application.
runtime/laravel is the Laravel adapter for the php-runtime ecosystem: it lets you run a Laravel app on a persistent worker runtime (Swoole, RoadRunner, FrankenPHP, ReactPHP) rather than one-process-per-request FPM. The package itself is thin glue — it does not ship a server. You always install it alongside a driver.
Minimal setup:
composer require runtime/laravel
composer require runtime/swoole # or runtime/frankenphp, runtime/roadrunner
Then create a single worker entrypoint at the project root (e.g. runtime.php), independent of public/index.php:
<?php
require __DIR__.'/vendor/autoload.php';
// Shape of the bootstrap; confirm exact class names against src/ of your pinned version
$app = require __DIR__.'/bootstrap/app.php';
\Runtime\Laravel\Runtime::get()
->run($app, '0.0.0.0:8080');
Where to look first, in order:
README.md — the example worker script is the whole API surface.src/ — expect one or two classes (an application/handler adapter plus a runner or service provider). Read them fully; there is very little code.tests/ and any examples/ directory — the fastest way to see the intended wiring for your driver version.runtime/swoole, runtime/frankenphp) for server lifecycle options (workers, max_requests, TLS, static files).First use case: take an existing JSON API, point the worker at Swoole locally, and confirm parity with php artisan serve before touching production. The win is booting the container once and reusing it across thousands of requests — measure that delta before investing further.
One bootstrap file per runtime, not per app. Keep runtime.php (Swoole) and runtime-frankenphp.php separate; they have different CLI flags, signal handling and extension requirements. Share the Laravel bootstrap between them.
Request lifecycle wiring. The adapter converts an incoming PSR-7 request into an Illuminate\Http\Request, pushes it through Illuminate\Contracts\Http\Kernel::handle(), then converts the Laravel response back. Keep this conversion in the package and do not reimplement it — but do understand it, because everything in the Gotchas section flows from it.
Reset scoped state between requests. This is the Octane problem, and it is yours to solve:
$app->forgetScopedInstances();
// plus anything you registered with $app->singleton() that should be per-request
If you already use laravel/octane, reuse its listeners (RequestReceived, RequestTerminated, OperationTerminated) — they are just event classes and apply equally here. If you don't, register your own terminate hook that clears auth guards, Log context, and any per-request caches.
Keep the artisan path untouched. The runtime only replaces the HTTP entrypoint. php artisan migrate, queue workers and the scheduler still run on plain CLI. Don't try to host the scheduler inside the worker.
Config and route caching are build-time steps. Bake php artisan config:cache and route:cache into your image, then start the worker. Env changes require a worker restart, not a request.
Supervise it like any long-lived process. systemd unit or supervisor program with restart=always, a readiness endpoint that actually hits the Laravel kernel, and SIGTERM forwarded so the runtime can drain in-flight requests. Set max_requests to a few thousand as a blunt memory-leak guard.
Testing. Run functional tests against a live worker (HTTP client against localhost:8080) rather than only $this->get() in PHPUnit. Unit tests never exercise the long-lived state path, which is exactly where this package changes behaviour.
Gradual adoption. Put the runtime behind the same nginx/CDN contract as FPM and canary it on one pod. You can run both entrypoints from the same codebase indefinitely.
static property, container singleton, or Event::listen registered at boot and mutated per-request will carry across users. Audit auth guards, URL::defaults(), Locale, Carbon's test clock, and Cache tag state. This is the number one source of bugs.$_SERVER, $_GET, $_FILES are not the per-request truth under a persistent worker. Use the injected Request object. Packages that read $_SERVER['HTTP_HOST'] directly (older SDKs, debug tools) will misbehave — test them explicitly.file/array either leak across requests or diverge across workers. Move to Redis or the database before you scale past one process.APP_DEBUG error pages retain request data in memory. Disable or aggressively prune them in workers; enable only for a single debugging process.dd(), exit(), die() kill the worker. Same for uncaught fatal errors outside the adapter's try/catch. Ensure fatal errors are logged and the process is restarted by the supervisor.public/ assets are not served by the runtime. Static files, uploads and storage:link still need nginx/FrankenPHP static handling. Configure it, or you'll ship a broken /storage path.composer.json, read the diff on every bump, and wrap the integration in your own bootstrap file so a rename in src/ is a one-line fix rather than a redeploy scramble.laravel/octane covers the same ground with far more mileage. Choose runtime/laravel when you specifically want the runtime-agnostic php-runtime abstraction (e.g. one codebase targeting Swoole and FrankenPHP and ReactPHP), and treat it as an early-adopter bet..rr.yaml. Get the Dockerfile right before blaming the package.How can I help you explore Laravel packages today?