skip to content

In Laravel Octane, what do octane:install and the OCTANE_SERVER variable decide, and how does octane:start choose which server to launch?

level: juniorimportance: must knowfreq 45%

answer

  1. installer writes one .env line
  2. publishes config/octane.php
  3. --server flag beats config
  4. config fallback is roadrunner
  5. binds 127.0.0.1:8000 by default

basics

~10 s

octane:install asks for FrankenPHP, RoadRunner or Swoole, sets up what that server needs, publishes config/octane.php and writes OCTANE_SERVER to .env. octane:start launches --server if given, otherwise config('octane.server'), on 127.0.0.1:8000.

solid answer

~30 s

After `composer require laravel/octane`, `php artisan octane:install` prompts for a server (`frankenphp` is preselected; `--server=` skips the prompt) and prepares it: the FrankenPHP binary plus `public/frankenphp-worker.php`, the `spiral/roadrunner-http` and `spiral/roadrunner-cli` packages plus the `rr` binary, or only a warning when the Swoole extension is missing. It then publishes `config/octane.php` and writes `OCTANE_SERVER=<choice>` into `.env`. `php artisan octane:start` takes `--server` first and otherwise `config('octane.server')`, which is `env('OCTANE_SERVER', 'roadrunner')`, and hands off to a hidden per-server command (`octane:frankenphp`, `octane:roadrunner`, `octane:swoole`). It binds `127.0.0.1:8000` unless `--host` or `--port` say otherwise. Open Swoole runs under the `swoole` value.

code

bash · 10 lines
bash
composer require laravel/octane

# Skip the prompt and choose the server explicitly
php artisan octane:install --server=frankenphp

# Uses OCTANE_SERVER from .env
php artisan octane:start

# Override the server and bind address for one run
php artisan octane:start --server=swoole --host=0.0.0.0 --port=9000

go deeper

for a junior

Recall the two commands and their jobs: octane:install sets things up once and writes OCTANE_SERVER, octane:start launches the server on 127.0.0.1:8000.

for a middle

Explain the resolution order: --server, then config('octane.server'), whose fallback is roadrunner, and the hidden per-server commands octane:start calls.

for a senior

Show you make the server choice explicit in production, in the env and in the process monitor's command, so a lost .env line cannot silently switch drivers.

for a principal

Weigh standardising one Octane driver across services against per-service choices, given that Swoole-only APIs and binary downloads change build images and portability.

## What Octane adds to a Laravel app **Laravel Octane** (`laravel/octane`, 2.20 at the time of writing) serves a Laravel application through a long-running **application server** instead of a fresh PHP process per request. Octane itself is the glue: Artisan commands to install, start, reload and stop a server, a worker loop that feeds requests into a booted application, and listeners that reset framework state between requests. The server underneath is one of three **drivers**: - `frankenphp` - FrankenPHP, a Go binary built on the Caddy web server. - `roadrunner` - RoadRunner, a Go process manager that talks to PHP worker processes. - `swoole` - the Swoole **or** Open Swoole PHP extension; Octane's Swoole code checks for either extension, so Open Swoole has no driver name of its own. ## What `octane:install` does `php artisan octane:install` is a one-time setup command. It asks which server you want (the prompt preselects `frankenphp`), or takes the answer from `--server=`, and then does server-specific work: | Choice | What the installer prepares | Files it adds to `.gitignore` | |---|---|---| | `frankenphp` | downloads the FrankenPHP binary for your OS, copies `public/frankenphp-worker.php` | `**/caddy`, `frankenphp`, `frankenphp-worker.php` | | `roadrunner` | offers to `composer require spiral/roadrunner-http` and `spiral/roadrunner-cli`, then downloads the `rr` binary | `rr`, `.rr.yaml` | | `swoole` | only checks the extension and warns "The Swoole extension is missing." if it is absent | none | Note what the Swoole row does **not** do: Octane never compiles or installs a PHP extension. You install `swoole` or `openswoole` yourself (typically with PECL, or a Docker image that ships it). When the server-specific step succeeds, the installer: 1. writes `OCTANE_SERVER=<choice>` into `.env`, replacing an existing `OCTANE_SERVER=` line or appending one; 2. publishes `config/octane.php` through `vendor:publish --tag=octane-config` (with `--force`, it overwrites an existing copy). ## How `octane:start` picks the server `php artisan octane:start` is the command you run, locally or under a process monitor. Its resolution order is short: 1. the `--server` option, if you passed one; 2. otherwise `config('octane.server')`, which the published config defines as `env('OCTANE_SERVER', 'roadrunner')`. It then calls a **hidden** per-server command - `octane:frankenphp`, `octane:roadrunner` or `octane:swoole` - forwarding the host, port, worker count and other options. An unknown value ends with "Invalid server: ..." and exit code 1. The config fallback is the trap worth remembering. The installer's prompt suggests FrankenPHP, but the config file's own default is still **RoadRunner**. If the `OCTANE_SERVER` line is lost - a new `.env` on a server, a container that does not pass it through - `octane:start` tries RoadRunner and fails with "RoadRunner not installed. Please execute the `octane:install` Artisan command." when RoadRunner was never set up. ## Defaults that come with the start command - **Host**: `--host`, else `config('octane.host')`, else the `OCTANE_HOST` variable, else `127.0.0.1`. The loopback default means only local clients (for example, a reverse proxy on the same machine) can reach Octane until you bind `0.0.0.0`. - **Port**: `--port`, else `config('octane.port')`, else `OCTANE_PORT`, else `8000`. - **Workers** and **max requests**: `--workers` defaults to `auto` and `--max-requests` to `500`; sizing and recycling are separate topics. - The start command also refuses to launch when the port is already in use, or when the chosen server is already running according to its state file (`storage/logs/octane-server-state.json` by default). ## Where the pieces end up After installing and starting once, the files involved are easy to list: - `config/octane.php` - the published config: `server`, `https`, listeners, `warm`/`flush`, `watch`, `max_execution_time`, `state_file` and per-server sections; - `.env` - the `OCTANE_SERVER` line the installer wrote; - `public/frankenphp-worker.php` - FrankenPHP's worker entry script, copied on install or first start; - `.rr.yaml` - created empty by the RoadRunner start command when you do not pass `--rr-config`, because Octane passes most RoadRunner settings as `-o` overrides; - the `frankenphp` or `rr` binary in the project root, which the start command also offers to download if it is missing or too old. ## Mistakes interviewers listen for - Treating `octane:install` as something you run on every deploy. It is setup; deploys use `octane:reload` or a restart. - Assuming the config falls back to FrankenPHP because the installer recommends it. - Looking for an `openswoole` driver value: Open Swoole runs under `swoole`. - Expecting `php artisan serve` to use Octane. It does not; Octane is started only through `octane:start` (or the hidden per-server commands, which Docker images sometimes call directly).

  • What happens if OCTANE_SERVER disappears from .env and nobody passes --server?
    `config('octane.server')` falls back to `roadrunner`, so `octane:start` launches the RoadRunner path. On an app that was installed for FrankenPHP or Swoole, that fails with "RoadRunner not installed. Please execute the `octane:install` Artisan command." The fix is to restore the variable or pass `--server` in the process monitor's command.
  • How do you run Octane on Open Swoole instead of Swoole?
    Install the `openswoole` extension instead of `swoole` and keep the server value `swoole`. Octane's Swoole code accepts either extension and switches to Open Swoole's table class when that one is loaded, so concurrent tasks, ticks, the Octane cache and tables behave the same way.
  • Why does octane:install edit .gitignore?
    The FrankenPHP and RoadRunner paths download OS-specific binaries (`frankenphp`, `rr`) and generate files such as `public/frankenphp-worker.php` or `.rr.yaml`. Those belong to the machine, not the repository, so the installer appends them to `.gitignore` if the file exists.

saying these in an interview costs you the question

  • config/octane.php falls back to FrankenPHP when OCTANE_SERVER is unset
  • octane:install compiles and installs the Swoole extension for you
  • Open Swoole needs its own openswoole server value in the config
  • octane:start binds 0.0.0.0 by default, so it is reachable from outside
  • php artisan serve switches to Octane once the package is installed