skip to content

Under Laravel Octane in local development, why does an edited route not show up until a restart, and what does octane:start --watch need to fix that?

level: middleimportance: should knowfreq 35%

answer

  1. app booted once per worker
  2. Node plus chokidar dev dependency
  3. octane.watch config paths
  4. --poll for mounted volumes
  5. FrankenPHP watches natively

basics

~20 s

Octane workers boot the app once and keep it in memory, so edited files are not reread. octane:start --watch runs a Node chokidar watcher over config('octane.watch') and reloads the workers on change; FrankenPHP uses its own watcher instead.

solid answer

~40 s

Under Octane the routes, providers and config are loaded when a worker boots, and later requests reuse that booted application, so a change to `routes/web.php` is invisible until the workers restart. `php artisan octane:start --watch` fixes that for development: for Swoole and RoadRunner, Octane spawns `node file-watcher.cjs`, which needs Node and the `chokidar` package (`npm install --save-dev chokidar`), watches the paths in `config('octane.watch')`, and on any change Octane reloads the workers. If Node or chokidar is missing the watcher dies and Octane exits with "Watcher process has terminated. Please ensure Node and chokidar are installed." For FrankenPHP, Octane writes the same paths into FrankenPHP's native `watch` directives instead. `--poll` switches chokidar to polling for network or Docker-mounted filesystems. It is a development tool, not a deploy mechanism.

code

bash · 8 lines
bash
# One-time: the watcher used by the Swoole and RoadRunner paths
npm install --save-dev chokidar

# Reload workers whenever a watched file changes
php artisan octane:start --watch

# Inside a container with a bind-mounted project
php artisan octane:start --watch --poll

go deeper

for a junior

Remember that Octane keeps the booted app in memory, so code edits need a worker reload, and that --watch automates it once Node and chokidar are installed.

for a middle

Explain the mechanics: the octane.watch paths, the Node watcher for Swoole and RoadRunner, FrankenPHP's native watcher, --poll, and that a watch event triggers a worker reload.

for a senior

Show judgment about keeping --watch out of production images and start commands, and about which changes a reload cannot apply.

for a principal

Frame the tradeoff between developer speed and fidelity: a watcher, per-request boots with --max-requests=1, or plain PHP-FPM locally while production runs Octane.

## Why edits do not show up Under PHP-FPM every request starts from nothing: PHP reads `public/index.php`, boots the framework, loads the routes and runs. **Laravel Octane** changes that. Each **worker** boots the application once - service providers, configuration, the route table - and then feeds request after request into that same booted application. The practical consequence in development is that an edit to `routes/web.php`, a controller or a config file is **not reread**. The worker keeps serving the version it booted with until it restarts. (Why that happens in worker-mode PHP in general is a runtime topic; here it matters only as the reason Octane needs a watcher.) ## What `--watch` does `php artisan octane:start --watch` asks Octane to reload the workers whenever an application file changes. How it watches depends on the server. | Server | Who watches files | Prerequisites | |---|---|---| | Swoole / Open Swoole | a Node process running Octane's `file-watcher.cjs` | Node on the PATH, `chokidar` in the project | | RoadRunner | the same Node + chokidar process | Node on the PATH, `chokidar` in the project | | FrankenPHP | FrankenPHP's own watcher, fed through `watch` directives in the generated Caddyfile | no Node needed | For the chokidar path, Octane starts the watcher next to the server and polls its output. On any add, change or delete event it prints "Application change detected. Restarting workers..." and calls the same **reload** used by `octane:reload` - a graceful worker restart, not a full server restart. If the watcher process exits (usually because `chokidar` is not installed), Octane stops with: > Watcher process has terminated. Please ensure Node and chokidar are installed. ## What gets watched The paths come from the `watch` key in `config/octane.php`. The published default is: - `app`, `bootstrap`, `routes` - `config/**/*.php`, `database/**/*.php`, `public/**/*.php`, `resources/**/*.php` - `composer.lock` and `.env` A few details matter in practice: - **Empty list**: with chokidar, an empty `watch` array throws an `InvalidArgumentException` asking you to update the config. FrankenPHP instead falls back to its own default watch pattern. - **chokidar 4 removed glob support.** Octane's watcher script handles this by splitting each pattern into a base directory and an optional extension filter, so `resources/**/*.php` still means "PHP files under resources". - **Front-end assets**: JavaScript and CSS go through Vite's own dev server; the Octane watcher is about PHP code and configuration the workers have already loaded. ## Polling and containers `--poll` passes a flag to the watcher that turns on chokidar's `usePolling`. Native file-system events often do not cross a network share or some Docker bind mounts, so edits made on the host never reach a watcher in the container. Polling checks the files on an interval instead, at a CPU cost. Octane's docs also show a different development shortcut for a Docker setup: `--workers=1 --max-requests=1`, which boots a fresh application for every request, trading speed for never serving stale code. ## Troubleshooting a watcher that seems to do nothing 1. **The watcher died**: look for the "Watcher process has terminated" message and install Node or `chokidar`. 2. **Events never arrive**: the project sits on a network share or a bind mount; add `--poll`. 3. **The file is not watched**: a local package or a custom directory outside the default list needs its own entry in `octane.watch`. 4. **The workers reloaded but nothing changed**: configuration may be cached, so the new workers read the old cached file; or the change is a server-level option that a reload does not apply. ## Where `--watch` does not belong 1. **Production.** Deploys should reload workers deliberately, after the new code is fully in place (`octane:reload` or a restart). A watcher reacting to a half-copied release can load a mix of old and new files. 2. **Server-level options.** A reload restarts workers, not the server process, so anything fixed when the server started - the port, the worker count, `max_execution_time` - still needs a stop and start. 3. **Dependencies.** Node and chokidar are development dependencies; a production image built without them will fail if `--watch` sneaks into its start command. ## Summary for the interview The short answer: Octane serves from memory, so code changes need a worker reload; `--watch` automates that locally, through Node and chokidar for Swoole and RoadRunner and through FrankenPHP's own watcher for FrankenPHP, using the paths in `octane.watch`.

  • Does a --watch reload pick up a change to --workers or max_execution_time?
    No. The watcher triggers the same graceful worker reload as `octane:reload`, which restarts workers inside the running server. Options fixed when the server started, such as the worker count, the port and `max_execution_time`, need `octane:stop` and a fresh `octane:start`.
  • Why is --watch a poor way to roll code out in production?
    It reacts to every file event, including the middle of a deploy that is still copying files, so workers can boot a mix of old and new code. It also needs Node and chokidar in the production image. Deploys should reload workers once, after the release is complete.

saying these in an interview costs you the question

  • Octane rereads route files on every request, like PHP-FPM does
  • --watch restarts the whole server process, so every option is re-read
  • FrankenPHP's --watch also needs Node and chokidar installed
  • --watch is a safe way to roll out code in production
  • Octane installs chokidar automatically during octane:install