Skip to main content

FPM and Octane/RoadRunner runtimes

MSK Laravel projects use PHP-FPM as the default runtime and the always-available fallback path. Laravel Octane with RoadRunner is a work-in-progress (wip), opt-in runtime for projects that need higher throughput.

Current validation status

The first long-running observation start was invalidated after an immutable-image contract defect was found and the system failed closed to FPM. A new observation has started with the corrected immutable image, but do not treat the transition as complete until that observation and the rollback gate both pass.

Choose a runtime​

ModePurposeOperating rule
FPMDefault production and incident recoveryUse without additional Octane configuration.
CanaryCompatibility checks with limited requestsUse only through a trusted operations path; never trust a public request header.
OctaneOptional runtime for a validated serviceRequires an audit, staged rollout, monitoring, and an FPM rollback path.

Octane uses long-lived workers, so it is not a drop-in PHP-FPM replacement. Before switching, remove code that retains request data in static properties or singletons, leaks user or tenant context between requests, or releases resources only when the process exits.

Prerequisites​

  1. Review the source, license, and vulnerabilities of the container image and RoadRunner binary.
  2. Supply the approved image repository and SHA-256 digest separately so Compose can construct only repository@sha256:digest. Reject mutable tags and arbitrary image strings.
  3. Validate authentication, authorization, tenant isolation, audit logs, locale, cache, and queues in development, test, and staging environments.
  4. Keep the FPM service and configuration available for immediate rollback.
  5. Prepare monitoring that can compare error rate, latency, memory, worker restarts, and database and Redis connections before and after the switch.

Configuration​

Configure these values in the project .env. Never commit real secrets or internal addresses to the repository.

VariableDescription
OCTANE_IMAGE_REPOSITORYSecurity- and license-reviewed image repository without a tag or digest
OCTANE_IMAGE_SHA256Approved 64-character SHA-256 digest, combined as repository@sha256:digest by Compose
MSK_HTTP_RUNTIMERouting mode: fpm, canary, or octane
OCTANE_WORKERSNumber of Octane workers; start conservatively and tune from measurements
OCTANE_MAX_REQUESTSMaximum requests handled before a worker is recycled
OCTANE_REQUEST_TIMEOUTRequest processing timeout
OCTANE_GRACEFUL_TIMEOUTTime allowed for in-flight requests during shutdown
OCTANE_HTTPSMakes Octane recognize HTTPS correctly behind a proxy
OCTANE_LOG_LEVELRoadRunner log level
PLG_OCTANE_RUNTIME_ENABLEDEnables the Octane runtime plugin. The Octane Compose overlay enables it.

Do not immediately match the worker count to the number of CPU cores. Increase it gradually while measuring database connections, Redis connections, and memory usage.

Start the runtime​

Run FPM with the project's base Compose configuration. Add the _docker/docker-compose.octane.yml overlay and the octane profile only when evaluating Octane.

The generic _template overlay uses source bind mounts and is intended for development and compatibility validation. Passing the command below does not prove the production immutable-image contract. Production deployment also needs a separate image containing the application, vendor, and the audited RoadRunner binary, plus digest-format validation, non-root and read-only execution, and resource limits.

# Default FPM configuration
docker compose -f _docker/docker-compose.amd64.yml up -d

# Opt-in Octane/RoadRunner configuration
docker compose \
-f _docker/docker-compose.amd64.yml \
-f _docker/docker-compose.octane.yml \
--profile octane up -d

On ARM64, replace only the base Compose file with its architecture-specific counterpart. Keep the Octane service on the application's internal Docker network. External traffic must continue to pass through the existing Nginx security, cache, and rate-limit layers.

Validate the transition​

Run the same scenarios before and after switching:

  • The health endpoint and public pages respond normally.
  • Login, logout, sessions, and secure cookies work correctly.
  • SaaS, Tenant, and Organization context and permissions never leak between requests.
  • Audit logs and request-specific locale and timezone remain correct.
  • Authenticated, personalized, and error responses are never cached incorrectly.
  • Nginx policies for search bots, scrapers, and human traffic remain unchanged.
  • Worker memory does not grow continuously, and workers recycle at the configured request limit.
  • Error rate, latency, and database and Redis connection counts stay within the approved range.

Do not approve production from short functional tests alone. The observation period must include natural traffic peaks and idle periods.

Roll back​

Stop the Octane rollout and return to FPM if any of these signals appear:

  • 5xx responses, timeouts, or worker restarts remain above the baseline.
  • User, tenant, permission, or locale state leaks between requests.
  • Memory or database and Redis connection counts continue to grow.
  • Authentication, cache, or audit-log behavior differs from FPM.

Keep FPM running as hot standby and use the project's validated runtime switch tool to atomically restore the Nginx upstream selector to FPM. Confirm the target health check, validate the Nginx configuration, and reload it; no data volume deletion or database reset is required. Repeat the validation checklist after returning to FPM, and block both automatic and manual Octane re-promotion until the cause is resolved.

Plugins and extension code​

OctaneRuntime 0.1.0 requires Core >=1.26.0. The plugin provides runtime lifecycle extension points and compatibility boundaries. Do not duplicate worker initialization and cleanup logic in each project. Use the plugin contracts and Laravel scoped bindings. The feature must degrade safely when its package or runtime is absent in an FPM environment. A nested synchronous queue job inside an active lifecycle fails closed to prevent context contamination; dispatch nested work to an asynchronous queue.

See the Core and plugin catalog and the public plugin catalog for related capabilities.