RoadRunner

Why RoadRunner

PHP-FPM boots the framework on every request. RoadRunner keeps workers alive — boot once, handle many requests. ConnectionManager reuses PDO across requests; call disconnect() only on worker shutdown.

Setup

composer require spiral/roadrunner-http nyholm/psr7
composer require --dev spiral/roadrunner-cli
php pionia rr:setup
php pionia runserver

For production deploy, run php pionia optimize --production and configure OPcache preload — see Production performance.

HTTP listen address

Resolved in order:

  1. CLI --port / --host
  2. PORT / SERVER_PORT in .env
  3. [roadrunner] or [server] in settings.ini
  4. .rr.yamlhttp.address
  5. Default 8003 (same as php pionia serve and the frontend API proxy)

runserver passes -o http.address=… when the resolved address differs from the file.

Commands

CommandPurpose
php pionia runserverForeground RR (formatted access logs in terminal)
php pionia runserver --detachBackground; logs to storage/logs/roadrunner.log
php pionia runserver:logsTail log file (Ctrl+C to stop)
php pionia stopserverStop detached instance

Log formatting

RoadRunner emits structured HTTP lines. Pionia formats them for readability:

13:18:42  GET     /api/v1/ping  200  1.2 KB   4ms

Use --raw on runserver or runserver:logs for the original JSON lines.

runserver:logs options: --lines=50, --no-follow, --wait (block until log exists), --log=/path.

Worker entry

worker.phpPioniaWorker routes by RR_MODE:

ModeHandler
httpHTTP request loop
jobsMoonlight job consumer
centrifugeWebSocket RPC (optional)

Never call exit() in route handlers — it kills the worker process.

Use handleRequest() / worker-safe patterns from request lifecycle.

Moonlight jobs

environment/settings.ini:

[jobs]
ENABLED = true
PIPELINE = moonlight
RPC = tcp://127.0.0.1:6001

.rr.yaml in your app root needs rpc + jobs sections when using background jobs.

moonlight()->async('mail', 'send_welcome', ['email' => $user->email]);

Returns returnCode: 202 with job_id when the queue accepts the job. See Background work.

Realtime (WebSockets, optional)

RoadRunner can host a Centrifugo plugin for WebSocket RPC using the same Moonlight { service, action } envelope as HTTP.

  1. composer require roadrunner-php/centrifugo
  2. Uncomment the centrifuge section in .rr.yaml
  3. Enable in environment/settings.ini:
[realtime]
ENABLED = true
CHANNEL_PREFIX = moonlight

Frames are handled by MoonlightFrameHandler — responses match the HTTP JSON shape. This is optional; most apps only need HTTP + jobs.

Built-in dev server vs RR

php pionia servephp pionia runserver
ServerPHP -SRoadRunner
WorkersNew process per requestPersistent pool
Jobs queueSync fallback after responseFull RR jobs
Best forQuick local API checksProduction-like testing

Config reference

See .rr.yaml in your project root for HTTP pool, jobs pipeline, and optional Centrifugo block.

Related: Maintenance mode · Database connections · CLI.