skip to content

In Laravel 13, what does php artisan reload do after a deploy, and how do packages like Horizon add their own restart commands?

level: middleimportance: should knowfreq 38%

answer

  1. long-running processes keep old code
  2. queue:restart and schedule:interrupt by default
  3. ServiceProvider::reloads() adds commands
  4. same key replaces the default
  5. a process monitor restarts them

basics

~10 s

php artisan reload terminates long-running processes so a process monitor restarts them on the new release: it runs queue:restart and schedule:interrupt, plus commands packages register with reloads(), such as horizon:terminate, octane:reload, reverb:restart and pulse:restart.

solid answer

~40 s

PHP-FPM boots the application afresh on every request, but queue workers, Horizon, Octane servers, Reverb and Pulse workers keep the previous release in memory. `php artisan reload` (`ReloadCommand`) runs a task list: `queue` => `queue:restart`, `schedule` => `schedule:interrupt`, then everything in `ServiceProvider::$reloadCommands`. Packages add entries with `$this->reloads('command', 'key')` in their provider: Horizon registers `horizon:terminate` under the key `queue`, which replaces `queue:restart`; Octane adds `octane:reload`, Reverb `reverb:restart`, Pulse `pulse:restart`. Most of these ask processes to exit gracefully, so a process monitor must be configured to start them again. `--except` skips entries by key or command name. It belongs at the end of the deploy, after `optimize`.

code

php · 16 lines
php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            // run our own ticket-sync daemon's restart on every php artisan reload
            $this->reloads('tickets:sync-restart', 'ticket-sync');
        }
    }
}

go deeper

for a junior

Recall that php artisan reload restarts long-running processes such as queue workers after a deploy.

for a middle

Explain the default tasks, how reloads() adds package commands, and why a process monitor must restart what reload stops.

for a senior

Place reload after caches and the release switch, and account for every long-running process, including your own daemons.

for a principal

Inventory which processes hold code in memory and make restarting them part of the platform's deploy contract.

## Why a deploy needs a reload step Under PHP-FPM every HTTP request starts from scratch: it loads the current code and the current caches. Several parts of a Laravel application do not work that way. They are **long-running processes** that booted once and keep running: - `queue:work` workers, or Horizon's supervisors; - an Octane application server; - a Reverb WebSocket server; - Pulse's ingest worker; - a long-running scheduler such as `schedule:work` or sub-minute tasks. After a deploy those processes still execute the previous release's classes and configuration. On a busy ticketing platform that can mean a worker sending order emails with the old template, or processing a job whose payload the new code changed. ## What `php artisan reload` runs `Illuminate\Foundation\Console\ReloadCommand` has a task list, much like `optimize`: | Key | Command | Effect | |---|---|---| | `queue` | `queue:restart` | tells workers to exit after their current job | | `schedule` | `schedule:interrupt` | tells a running scheduler to stop | | package keys | anything in `ServiceProvider::$reloadCommands` | whatever each package registered | Each task runs with `callSilently()` and shows `DONE` or `FAIL`. Like `optimize`, the command accepts `--except` with a key or a command name. ## How packages plug in `Illuminate\Support\ServiceProvider` has a protected `reloads(string $reload, ?string $key = null)` method. It stores the command in the static `$reloadCommands` array under a key, which defaults to a kebab-case name derived from the provider class. First-party packages in their current releases call it from their providers: - Horizon: `$this->reloads('horizon:terminate', 'queue')` - Octane: `$this->reloads('octane:reload', 'octane')` - Reverb: `$this->reloads('reverb:restart', 'reverb')` - Pulse: `$this->reloads('pulse:restart', 'pulse')` Horizon's choice of the key `queue` is deliberate. `ReloadCommand` spreads the package array after its defaults, so an entry with the same key **replaces** the default: with Horizon installed, `reload` runs `horizon:terminate` instead of `queue:restart`, because Horizon manages its own workers. These packages guard the call with `method_exists($this, 'reloads')`, so they still install on framework versions that predate the method. ## What the default tasks actually do - `queue:restart` stores a restart timestamp in the cache; each worker checks it between jobs and exits after finishing the job in hand, so no job is cut off midway. - `schedule:interrupt` puts the cache key `illuminate:schedule:interrupt` until the end of the current minute; a `schedule:run` process that is looping for sub-minute tasks checks it and stops early, so the next minute starts on the new code. Both rely on the cache, so the processes and the deploy must share the same cache store. `--except` skips entries: `php artisan reload --except=schedule` leaves the scheduler alone. ## The process monitor is not optional Most reload commands ask processes to **exit**; they do not start new ones. Something must notice the exit and start a fresh process: a process monitor such as Supervisor, a systemd unit, or the hosting platform. Without one, `reload` simply stops your workers. The docs note that on Laravel Cloud the reload is handled automatically. ## Where it sits in the deploy 1. install dependencies; 2. migrate; 3. `php artisan optimize`; 4. switch traffic to the new release, if you use release directories; 5. `php artisan reload`. Reloading earlier risks workers booting before the new caches exist, or before the new code is live. ## Version note In older Laravel releases a deploy script listed each restart command by hand (`queue:restart`, `horizon:terminate`, `octane:reload` and so on). Laravel 13 has the single `reload` command and the `reloads()` hook, so the script needs one line and new packages join automatically.

  • With Horizon installed, why does php artisan reload not run queue:restart?
    Horizon's provider calls `$this->reloads('horizon:terminate', 'queue')`. `ReloadCommand` spreads package entries after its defaults, and a string key that already exists is overwritten, so the `queue` task becomes `horizon:terminate`. Horizon then terminates its supervisors gracefully and the process monitor restarts them.
  • Why can php artisan reload leave a Laravel app with no queue workers at all?
    `queue:restart` and the package commands ask processes to exit; they do not start replacements. If workers were started by hand or by a monitor that does not restart exited processes, nothing brings them back. A process monitor configured to restart them is a prerequisite.

A shift change at a box office: the manager announces the new procedures once, every clerk finishes the customer in front of them and steps away, and the rota brings in fresh clerks who start with the new rules.

saying these in an interview costs you the question

  • php artisan reload is what makes PHP-FPM serve new code
  • reload starts new queue workers itself
  • reload kills jobs mid-execution
  • Packages must be added to the deploy script by hand in Laravel 13
  • Run reload before optimize so workers build the caches