skip to content

Shipping & Running

Taking a Laravel app live: the optimize caches, the /up health route and maintenance mode, a repeatable deploy, the hosting options and Pulse metrics. Interviewers ask what breaks after a deploy.

on this pageshow

explore

questions

24

What are the core steps of a Laravel 13 production deploy script, in order, and why do composer install and migrate need --no-dev and --force?

level: juniorimportance: must knowfreq 65%

answer

  1. code, dependencies, assets, schema, caches, processes
  2. require-dev stays off the server
  3. production confirm prompt cancels migrate
  4. composer install deletes config.php
  5. optimize after install, reload last

basics

~20 s

Fetch the code, run composer install --no-dev, build front-end assets, run php artisan migrate --force, run php artisan optimize, then php artisan reload. --no-dev skips require-dev packages; --force skips migrate's production confirmation, which a non-interactive script would otherwise cancel.

solid answer

~40 s

A typical order is: fetch the release; `composer install --no-dev --no-interaction`; `npm ci && npm run build`; `php artisan migrate --force`; `php artisan optimize`; `php artisan reload`. `--no-dev` leaves out `require-dev` (in the skeleton: Faker, Pail, Pint, PHPUnit, Mockery, Collision), which production neither needs nor should expose. `migrate` uses Laravel's confirmation prompt whenever the environment is `production`; in a non-interactive script that prompt resolves to its default of "no", the command prints "Command cancelled." and exits with a failure, so the flag is required. Order matters: Composer's `post-autoload-dump` script deletes `bootstrap/cache/config.php`, `services.php` and `packages.php`, so `optimize` must come after `composer install`, and `reload` comes last so restarted workers boot the new code and caches.

code

bash · 10 lines
bash
set -e
cd /var/www/tickets
git pull origin main

composer install --no-dev --no-interaction --prefer-dist
npm ci && npm run build

php artisan migrate --force
php artisan optimize
php artisan reload

go deeper

for a junior

Recall the order: code, composer install --no-dev, assets, migrate --force, optimize, reload, and what each flag is for.

for a middle

Explain why migrate needs --force in production, and why optimize must follow composer install because post-autoload-dump deletes cached files.

for a senior

Make the script fail fast, keep dev packages and stale manifests off servers, and restart every long-running process after caches are built.

for a principal

Standardise one deploy contract across services so ordering mistakes are caught by tooling rather than by an outage.

## The shape of a deploy A busy ticketing platform deploys several times a week. Whatever tool runs the script (a CI job, a hosting panel, Envoy, a shell script over SSH), the Laravel-specific steps are the same: 1. **Get the code** for the release (a `git` checkout or an unpacked artifact). 2. **Install PHP dependencies:** `composer install --no-dev --no-interaction`. 3. **Build front-end assets:** `npm ci` and `npm run build`, producing `public/build`. 4. **Apply schema changes:** `php artisan migrate --force`. 5. **Build Laravel's caches:** `php artisan optimize`. 6. **Restart long-running processes:** `php artisan reload`. In-place deploys often wrap steps 2 to 5 in maintenance mode; zero-downtime deploys build a separate release directory instead. Either way, the order above is the part interviewers probe. ## Why `--no-dev` `composer install` installs both `require` and `require-dev` by default. The skeleton's `require-dev` lists `fakerphp/faker`, `laravel/pail`, `laravel/pint`, `mockery/mockery`, `nunomaduro/collision`, `phpunit/phpunit` and `laravel/pao`. None of them belongs on a production server: - they add code and attack surface that production never runs; - they make installs slower and larger; - they can mask a mistake, such as production code that accidentally uses Faker, which would otherwise fail loudly. Composer's `post-autoload-dump` hook then runs `php artisan package:discover`, which rebuilds `bootstrap/cache/packages.php` from the packages actually installed, so dev-only service providers drop out of the discovered list. ## Why `--force` `migrate` uses Laravel's `ConfirmableTrait`. When `app()->environment()` is `production`, the command shows an **Application In Production** alert and asks "Are you sure you want to run this command?" with a default of no. A deploy script has no one to answer, so the prompt resolves to its default and the command prints "Command cancelled." and returns a failure code. `--force` skips the question. The same flag appears on other destructive commands for the same reason. ## Why the order matters | Step | Must come after | Reason | |---|---|---| | `composer install` | code checkout | installs what the new `composer.lock` says | | `migrate --force` | `composer install` | migrations and the framework must be the new version | | `optimize` | `composer install` | `post-autoload-dump` deletes `bootstrap/cache/config.php`, `services.php` and `packages.php` | | `optimize` | code checkout | caches must describe the new config, routes, events and views | | `reload` | `optimize` | restarted workers should boot the new code and fresh caches | The second `optimize` row is the subtle one. `Illuminate\Foundation\ComposerScripts::postAutoloadDump()` calls `clearCompiled()`, which removes the cached config, services and packages files. Running `optimize` first and `composer install` second leaves production running without a config cache at all. ## In place or side by side The script above updates files in the directory that is serving traffic. For the seconds it runs, a request can meet new PHP files with an old `vendor/`, or new code before its migration. Two ways out: - wrap the risky steps in maintenance mode, accepting a short outage; - build each release in its own directory and switch a symlink when it is complete, which is the zero-downtime approach. Either way the Laravel steps and their order stay the same; only where they run changes. ## Checking the result - Request the health route and a few key pages right after `reload`. - Watch the application log for the first minutes of traffic. - Confirm workers restarted: their process start times should be after the deploy. ## What the script should not do - **Run `optimize:clear` as a routine step.** It also flushes the default cache store; `optimize` alone replaces the old cache files. - **Install with `--no-scripts` on an in-place deploy.** Without `package:discover`, a `packages.php` left from an earlier install can still list dev-only providers that are no longer installed. - **Skip `reload`.** Queue workers and other long-running processes keep the previous release's code in memory until they restart. - **Continue after a failed step.** Use `set -e` or the deploy tool's equivalent so a failed migration stops the deploy before caches and restarts. ## Summary Code, dependencies without dev packages, assets, forced migrations, caches, reload: each step prepares what the next one relies on.

  • Why does running php artisan optimize before composer install leave a Laravel app without a config cache?
    The skeleton's `post-autoload-dump` Composer script calls `Illuminate\Foundation\ComposerScripts::postAutoloadDump()`, which deletes `bootstrap/cache/config.php`, `services.php` and `packages.php`. Any `composer install` that dumps the autoloader therefore wipes the config cache built earlier, so `optimize` has to run after it.
  • What happens if a Laravel deploy script runs php artisan migrate without --force on a production server?
    Because the environment is `production`, `ConfirmableTrait` asks for confirmation with a default of no. With no interactive terminal the default is taken, the command prints "Command cancelled." and returns a failure code, so no migrations run and a script using `set -e` stops there.
  • Why can composer install --no-dev --no-scripts break an in-place Laravel deploy?
    `--no-scripts` skips `package:discover`, so a `bootstrap/cache/packages.php` left from an earlier install with dev packages can still list providers such as a dev tool's service provider. Laravel then tries to register a class that `--no-dev` did not install. Letting the scripts run rebuilds the manifest from what is installed.

saying these in an interview costs you the question

  • migrate runs without prompting as long as APP_DEBUG is false
  • --no-dev also removes unused production packages
  • Run optimize first so composer install uses the cached config
  • optimize:clear must run at the start of every deploy
  • Queue workers pick up new code without a restart
open as a page

In the Laravel ecosystem, how do Laravel Cloud, Laravel Forge and Laravel Vapor differ in who runs the servers your app lives on?

level: juniorimportance: must knowfreq 50%

basics

~20 s

Laravel Cloud runs the app for you on fully managed, auto-scaling compute with managed databases, caches and object storage; Forge provisions and manages servers you own on a provider; Vapor deploys the app serverless onto AWS Lambda.

open as a page

In Laravel 13, what does php artisan down do to incoming requests, and how do --secret and --with-secret let the team keep using the site?

level: juniorimportance: must knowfreq 55%

basics

~20 s

php artisan down makes Laravel answer requests with a 503 maintenance response. With --secret=token (or a random --with-secret), visiting /token sets a signed laravel_maintenance bypass cookie so that browser uses the site normally; php artisan up ends maintenance.

open as a page

In Laravel 13, what does php artisan optimize cache, and what does php artisan optimize:clear remove?

level: juniorimportance: must knowfreq 62%

basics

~10 s

php artisan optimize runs config:cache, event:cache, route:cache and view:cache, plus any commands packages register. php artisan optimize:clear runs the matching :clear commands, and it also runs cache:clear and clear-compiled.

open as a page

After installing Laravel Pulse, why does the /pulse dashboard return 403 in production, and how do you grant access to it?

level: juniorimportance: must knowfreq 30%

basics

~20 s

Pulse registers a default viewPulse gate that only passes in the local environment, and its Authorize middleware checks that gate on /pulse, so production answers 403. Define viewPulse yourself in AppServiceProvider::boot, for example allowing only admins.

open as a page

In Laravel 13, what does the built-in /up route check, how do you add a database check, and what does it return during maintenance?

level: middleimportance: must knowfreq 45%

basics

~20 s

/up, registered by withRouting(health: '/up'), returns 200 when the app boots and every DiagnosingHealth listener succeeds, and 500 if a listener throws. It is excluded from maintenance mode, so it stays 200 while the site is down.

open as a page

After a volunteer-scheduling Laravel app's first deploy ran php artisan optimize, why do hot-fixed routes, config edits and new listeners have no effect?

level: middleimportance: must knowfreq 55%

basics

~20 s

The config, route and event caches from the earlier optimize are still in bootstrap/cache, and Laravel uses them instead of reading config files, route files or listener directories. Hot-fixed changes stay invisible until php artisan optimize runs again.

open as a page

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%

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.

open as a page

After a root-run deploy, why can a Laravel site fail with 'laravel.log could not be opened in append mode', and how is it prevented?

level: middleimportance: should knowfreq 40%

basics

~20 s

An Artisan command run as root created storage/logs/laravel.log (or a cache file) owned by root, so the PHP-FPM user can no longer write to it. Run deploy commands as the web user and give storage and bootstrap/cache group-writable ownership.

open as a page

An app deployed on Laravel Vapor loses user uploads and file-cache entries between requests; why does that happen, and what should it use instead?

level: middleimportance: should knowfreq 30%

basics

~20 s

On Vapor the app runs on AWS Lambda, where only /tmp is writable; vapor-core moves the storage path to /tmp/storage, which belongs to one short-lived container. Keep files on S3 and cache and sessions in DynamoDB, Redis or a database.

open as a page

In Laravel 13, what do php artisan down's --render, --retry, --refresh and --redirect options do, and why does --render matter during a deploy?

level: middleimportance: should knowfreq 35%

basics

~20 s

--render stores a prerendered view that public/index.php can serve before the framework loads; --retry sets Retry-After; --refresh sets a Refresh header; --redirect sends browsers to one path. --render keeps the page working while vendor files are being replaced.

open as a page

In Laravel 13, can routes defined with closures be cached by php artisan route:cache, and what can still make route caching fail?

level: middleimportance: should knowfreq 38%

basics

~20 s

Yes. Laravel 13's route:cache wraps each closure action in SerializableClosure::unsigned() and stores it in the cached route file, so closure routes work cached. Caching still fails when a closure captures a value PHP cannot serialize.

open as a page

After a release a Laravel news app feels slower; how do Pulse's slow-request and slow-query recorders and thresholds pinpoint the culprit endpoint?

level: middleimportance: should knowfreq 25%

basics

~20 s

Pulse's SlowRequests recorder logs requests at or above a threshold (1,000 ms by default), grouped by method, route URI and controller action; SlowQueries groups slow SQL with the file and line that ran it. Compare the cards before and after the release.

open as a page

Laravel Vapor runs no long-lived queue:work process; how are queued jobs and scheduled tasks executed there, and what does that change operationally?

level: seniorimportance: should knowfreq 25%

basics

~20 s

On Vapor, each SQS message triggers a Lambda invocation that runs the hidden vapor:work command to process that one job, and a per-minute invocation runs vapor:schedule, which calls schedule:run. There are no worker daemons to supervise or restart.

open as a page

A Laravel 13 streaming site runs on four web servers; after php artisan down on one of them most subscribers still reach the app mid-migration; why, and how does APP_MAINTENANCE_DRIVER=cache fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The default file driver writes storage/framework/down on the one server that ran the command, so the other three stay live. APP_MAINTENANCE_DRIVER=cache stores the flag in a shared cache store, so one php artisan down covers every server.

open as a page

A Laravel 13 release's caches were built by php artisan optimize in a CI job that lacked production environment variables; what breaks in production, and which caches are safe to build there?

level: seniorimportance: should knowfreq 32%

basics

~20 s

config:cache freezes resolved values, so production runs on the CI job's credentials, key and paths and ignores its own environment. Route, event and view caches mostly depend on code; cache config where production values exist.

open as a page

In Laravel Pulse, what changes when you set PULSE_INGEST_DRIVER=redis, and what must you run and trim to keep it healthy?

level: seniorimportance: should knowfreq 18%

basics

~20 s

With PULSE_INGEST_DRIVER=redis, requests push Pulse entries onto a Redis stream instead of writing to the database; a long-running php artisan pulse:work moves them into Pulse's tables, trims old data, and must be restarted with pulse:restart on deploy.

open as a page

On a high-traffic Laravel app, how does a Pulse recorder's sample_rate work, and which recorders is it safe to sample?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Each Pulse recorder's sample_rate is the chance, per event, that it is considered at all; the dashboard scales sampled counts up and marks them with ~. Sample high-volume recorders such as user requests and cache interactions, not rare events like exceptions.

open as a page

A small agency must host twenty client Laravel apps of very different sizes; how would you choose between Laravel Cloud, Forge-managed servers and Vapor?

level: principalimportance: should knowfreq 20%

basics

~20 s

Tier the portfolio: pack small, steady sites onto a few Forge-managed servers for cost and control, put spiky or high-stakes apps on Laravel Cloud to offload operations and scaling, and use Vapor only where serverless scaling on AWS outweighs its constraints.

open as a page

With Laravel Envoy, how do @servers, @task and @story describe a deploy, and what happens when one command in a task fails?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Envoy.blade.php declares @servers, shell @task blocks for named servers, and @story blocks that run tasks in order. Each task runs under set -e, so a failing command fails the task and stops the story unless --continue is passed.

open as a page

In Laravel 13, what can the Cloud facade tell your code, and what does the framework configure automatically when an app runs on Laravel Cloud?

level: middleimportance: nice to knowfreq 12%

basics

~20 s

Laravel 13's Cloud facade offers hosted(), usesManagedQueues(), queue() and isManagedQueue(); separately, on Cloud the framework reads platform-injected variables to set up object-storage disks, a read replica, Postgres pooling, a managed cloud queue connection, logging and exception reporting.

open as a page

In a Laravel package's service provider, how does optimizes() hook the package's own cache commands into php artisan optimize and optimize:clear?

level: middleimportance: nice to knowfreq 14%

basics

~10 s

Calling $this->optimizes(optimize: 'package:optimize', clear: 'package:clear-optimizations') in a provider's boot() adds those Artisan commands to the task lists that php artisan optimize and optimize:clear run after Laravel's own caches.

open as a page

Why does the Laravel Pulse Servers card stay empty after installation, and what does the pulse:check command do on each server?

level: middleimportance: nice to knowfreq 12%

basics

~20 s

The Servers card is fed by the Servers recorder, which only reacts to beats emitted by php artisan pulse:check; until that long-running command runs on each server, nothing records CPU, memory or disk. Each server needs a unique PULSE_SERVER_NAME.

open as a page