# Laravel Octane

> Run Laravel on Octane with Falak — FrankenPHP worker mode, Swoole or RoadRunner — with automatic port allocation and Caddy holding requests on deploy.

Source: https://falak.sh/docs/deploy/laravel-octane/

Laravel Octane boots your app once and serves many requests from memory. Falak supervises Octane for you, gives it a private port on every server, and switches the edge to it only after it answers. Deployments restart Octane while Caddy holds incoming requests, so users see a short latency spike instead of errors.

## Prerequisites

- A Laravel site deployed with Falak. See [Deploy Laravel](/docs/deploy/laravel/).
- `laravel/octane` installed in your app:

  ```bash
  composer require laravel/octane
  php artisan octane:install --server=frankenphp
  ```

  Commit the result (for FrankenPHP this includes `public/frankenphp-worker.php`).
- For **Swoole**: the `swoole` or `openswoole` PHP extension on your servers. For **RoadRunner**: the `rr` binary and `spiral/roadrunner-http` in your app. Falak installs neither.

## Choose an Octane server

| Octane server | API value | Available on | Default |
|---|---|---|---|
| FrankenPHP (worker mode) | `frankenphp` | FrankenPHP sites only | Default on FrankenPHP |
| Swoole | `swoole` | PHP-FPM or FrankenPHP sites | Default on PHP-FPM |
| RoadRunner | `roadrunner` | PHP-FPM or FrankenPHP sites | |

## Turn Octane on

1. Open the site's **Settings → Laravel**.
2. Turn on **Octane** and pick the server (FrankenPHP is preselected on FrankenPHP sites).
3. Save. Falak allocates a port and starts Octane on every server of the site (once the site has a live release).
4. The **Processes** block shows the routing state per server: `starting`, then `listening` once Octane answered. Only then does the edge proxy to it.

The site card and panel header show an **Octane** badge.

Through the API:

```bash
curl -X PUT https://falak.example.com/api/v1/sites/shop/laravel \
  -H "Authorization: Bearer $FALAK_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"octane": true, "octane_server": "frankenphp"}'
```

```json title="Response"
{"data": {"scheduler": true, "horizon": false, "octane": true, "maintenance": false, "octane_server": "frankenphp", "octane_port": 8412}}
```

## How it works

**Port allocation.** Falak picks the port, you cannot set it. The first candidate is `8000 + crc32(site id) % 1000`; if that is taken on any of the site's servers, it takes the next free one. A port is free when no other site uses it as an app port, Compose host port, Octane port, or Octane auxiliary port (port + 10000, used for FrankenPHP's `--admin-port` or RoadRunner's `--rpc-port`). The port is kept when you switch Octane off and on again, and re-checked when you add servers.

**The program.** Falak supervises `<slug>.octane`:

```bash title="Supervised command (FrankenPHP)"
php8.4 artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8412 --admin-port=18412
```

**Routing.** Until Octane answers, the site keeps being served the normal way (FrankenPHP `php_server` or PHP-FPM). A probe waits up to 60 seconds for any HTTP answer on `127.0.0.1:<port>`. Once verified, the edge serves existing files under `public/` directly (never `*.php`, dotfiles or directories) and proxies everything else to Octane, retrying the upstream for up to 30 seconds.

**Deploys.** Octane resolves the `current` symlink once at start, so a reload would keep serving the old release. After activation, Falak **restarts** Octane (the program's environment carries the new `FALAK_RELEASE_ID`). The old process gets 10 seconds to stop; meanwhile Caddy holds requests until the new process listens. **Restart processes** in the UI (same release) sends `octane:reload` instead.

**Switching off.** The route turns `draining`: the edge serves the site directly again, and the Octane program stops once no applied edge configuration proxies to it (or after 10 minutes).

## Limits

- Falak installs neither Swoole nor RoadRunner. Until they work, the probe keeps the site served directly.
- Octane's FrankenPHP server binds `:<port>` on all interfaces (Octane has no bind option). The server firewall's default-drop policy blocks it from outside.
- Octane under FrankenPHP uses the PHP embedded in the FrankenPHP binary, not the site's `phpX.Y` CLI version.
- If a verified Octane process crashes later, it is not un-routed automatically: requests get a 502 after the 30-second retry window, and the **Process keeps crashing** alert fires.

## Troubleshooting

| Symptom | Fix |
|---|---|
| State stays `starting` | Check the site's **Logs** for the `octane` program. Common causes: `laravel/octane` not installed, missing `public/frankenphp-worker.php`, missing Swoole extension. |
| State `failed` | The probe got no answer in 60 seconds. Fix the error, then redeploy or restart processes. |
| Code changes not visible after deploy | You changed the deploy script to skip `$FALAK_RESTART_PROCS`. Keep it. |
| Per-request state leaking between requests | Octane keeps your app in memory. Follow the [Octane guidelines](https://laravel.com/docs/octane) for singletons and static state. |

## Next steps
