Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Roadrunner Laravel Laravel Package

spiral/roadrunner-laravel

RoadRunner-powered application server for Laravel: replaces PHP-FPM with a high-performance Go worker, boosting throughput and cutting latency. Includes config publishing, Laravel integrations, and support for HTTP and queues via a simple composer install.

View on GitHub
Deep Wiki
Context7

Getting Started

The bridge turns a standard Laravel app into a resident-worker application served by the RoadRunner binary instead of PHP-FPM. Day-to-day, that means three moving parts you need to keep in sync: the Composer package, the rr binary, and .rr.yaml.

Minimal setup:

composer require roadrunner-php/laravel-bridge
./vendor/bin/rr get-binary          # download the matching rr binary
php artisan vendor:publish --tag=rr-config   # config/roadrunner.php + .rr.yaml
./rr serve                          # instead of php artisan serve

Where to look first:

  1. .rr.yaml — server-level concerns: http.pool, jobs.pool, kv, temporal, reload, logs. This is the file you'll tune for workers, timeouts, and allocation.
  2. config/roadrunner.php — Laravel-level concerns: HTTP middleware/kernel wiring, static file handling, cache store, queue connector, Temporal worker settings.
  3. WorkerOptions (max jobs, max tries, backoff, timeouts) — the knobs that decide how long a worker survives and how jobs are retried.

The first real use case is almost always "run my existing HTTP app under rr serve and confirm middleware, sessions, and auth behave the same." Do that before touching queues or Temporal — it isolates worker-lifecycle bugs from integration bugs.

If you only need queues, you can skip the HTTP side entirely: configure a jobs pipeline in .rr.yaml, point Laravel's queue connection at the RoadRunner driver, and run ./rr serve as your queue supervisor in place of queue:work.

Implementation Patterns

HTTP workers. Keep the normal Laravel HTTP kernel; the bridge wraps it. Register middleware the Laravel way, but treat anything bound as a singleton as suspect — it survives between requests. Prefer scoped() bindings for request-dependent services, and reset stateful singletons explicitly (auth guards, Request, UrlGenerator roots, view shared data).

Queues. Dispatch jobs with the ordinary dispatch() API and let the RR jobs driver consume them. Two patterns worth adopting:

  • Treat WorkerOptions::$maxTries as the single source of truth for retry limits, and set it explicitly. Null now falls back to 10 (changed in 6.6.2), so code that relied on an unset value meaning "unlimited" or "Laravel default" will behave differently.
  • Use array backoff (backoff: [10, 30, 60]) deliberately; 6.6.2 makes release() honour the array. Before this, an array backoff could collapse into a single value on release.
  • Implement failed(?Throwable $e) defensively. 6.6.2 added handling for jobs that fail without an exception, so your failed() method can now receive null where it previously wouldn't have been called at all.

Cache. Use the RoadRunner cache store for hot, short-lived data (rate limit counters, idempotency keys, locks). increment/decrement now preserve the remaining TTL (6.6.2) instead of resetting or dropping it — this matters for sliding-window counters, so re-verify any rate limiter that assumed the old behaviour.

Temporal. Register workflows/activities through the Temporal worker config, and keep activities thin: they run in the worker process and share its lifecycle. As of 6.6.2, exceptions thrown from failed activities are rethrown rather than swallowed, so try/catch blocks in workflow code will start seeing errors they previously missed. That's good — but it will surface latent bugs on upgrade.

Local vs production. Run the same .rr.yaml in both where possible. Use reload in dev so file changes restart workers; disable it in production and use rr reset / a signal-based restart during deploys so in-flight requests and jobs drain within destroy_timeout.

Deployment sketch. Build an image containing the rr binary, run ./rr serve as PID 1, add a health check against the HTTP endpoint, and make sure your orchestrator sends SIGTERM and waits at least as long as destroy_timeout. Pin the binary version alongside the Composer package — mismatches produce confusing protocol errors rather than clean failures.

Testing. Feature tests keep working through Laravel's normal test client; you don't need rr serve for them. Reserve real rr serve runs for smoke tests of middleware, sessions, and queue round-trips. Use Queue::fake() as usual for dispatch assertions.

Gotchas and Tips

  • State leakage is the number one bug class. Long-lived workers retain static properties, container singletons, and closed-over request data. If something works on the first request and breaks on the second, it's almost always this.
  • dd(), exit, die, and uncaught fatals kill the worker. RoadRunner will restart it, but you'll see dropped requests. Use proper exceptions and let the error handler render.
  • php artisan serve is not the dev server here. Use ./rr serve. Mixing the two, or leaving an old worker pool running, produces "config changed but nothing happened" confusion.
  • Config caching. php artisan config:cache plus a running worker pool means stale config until you restart. Bake config at image build time and restart workers on change.
  • Timeouts and payload limits are in .rr.yaml, not PHP. max_request_size, allocate_timeout, destroy_timeout, and pool max_jobs all bite before php.ini limits do. Large uploads and slow endpoints commonly need tuning there.
  • max_jobs interacts with memory. Setting it too high exposes leaks; set it low (e.g. 500) and rely on worker recycling rather than chasing every leak.
  • Release delays. 6.6.2 fixed release() converting delay to seconds. If you previously compensated with millisecond values in your job code, remove that workaround after upgrading or your jobs will be delayed 1000×.
  • Malformed job payloads. 6.6.2 tolerates non-object task payloads instead of blowing up; if you parse $job->payload() yourself, don't assume it's an object.
  • Debugging. ./rr serve -d -v for verbose output; rr workers to inspect pool state; PHP application logs go to stderr/stdout depending on config, so check your log driver actually writes somewhere persistent.
  • Extension points worth knowing: the HTTP middleware list in config/roadrunner.php, custom queue connectors, Temporal activity registries, and worker bootstrappers. Most "the bridge doesn't support X" problems are solvable with a middleware or a connector registration rather than a fork.
  • Upgrade posture for 6.6.2: no breaking changes, but the retry/backoff/TTL/Temporal-exception fixes change observable behaviour. Treat it as a behaviour-change release: run your queue failure tests and rate limiter tests after upgrading, and confirm anything relying on maxTries being null or on backoff arrays actually still retries the way you expect.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin