Skip to main content

Production Preflight for Laravel

A misconfigured production application rarely fails. APP_DEBUG=true serves pages, QUEUE_CONNECTION=sync works through every job, MAIL_MAILER=log accepts every message, and a key set twice in .env is valid syntax. Nothing turns red, and the problem shows when someone goes looking for it, or when a customer calls.

This package checks for those states twice: once in the deploy, so they never go live, and then on a schedule, because half of them appear after the go-live. A value changed on the server without config:cache, workers that never heard queue:restart, a migration nobody ran.

The checks are laravel-health checks, so they report through what the application already runs: its result store, its notifications and its monitoring endpoint. The deploy gets its own command, with an exit code that stops it.

Installation​

composer require pushery/production-preflight-for-laravel

The service provider is registered automatically through package discovery. The package requires spatie/laravel-health, which Composer installs alongside.

Quick start​

Stop a deploy on a finding. In the deploy script, after composer install and before the application goes live:

php artisan preflight --deploy

It exits with 0 when no check failed, 1 when one did, and 2 when a check could not look. A warning is printed and lets the deploy through. Deploying has the complete script for Forge.

Report in operation. Register the checks with laravel-health, in the boot method of a service provider:

use Pushery\ProductionPreflight\Preflight;
use Spatie\Health\Facades\Health;

Health::checks(Preflight::checks());

laravel-health then runs them with health:check, which the application schedules. In operation covers the schedule and the checks that need more than the defaults.

Look yourself. On the server, without --deploy, the command runs every check and prints the report:

php artisan preflight

What it checks​

Twenty-seven checks in three groups, sorted by what they need in order to answer. The checks page lists each with its verdict.

GroupNeedsRuns
The .env filethe file, read as textin a pipeline, in the deploy, on a schedule
The configurationthe loaded configurationin the deploy and on a schedule
The installationthe server itselfon a schedule only

Each check answers with one of four verdicts: pass, warning, fail, or not checked. "Not checked" is never a pass. A check that could not look, a missing file or a fact the command line cannot see, says so, and the deploy command exits with 2 for it.

Configuration​

Publish the configuration file, where every option is documented inline:

php artisan vendor:publish --tag=production-preflight-config
TagPublishes
production-preflighteverything below, in one command
production-preflight-configthe configuration file
production-preflight-langthe translation files

The checks report in the application's locale, in English, German, Spanish, French, Italian, Dutch or Portuguese. A regional locale reads the catalog of its language: pt-BR and pt-PT read pt, de-AT reads de. A file you publish for the region under lang/vendor/production-preflight/pt-BR/ wins over the language key by key.

A setting that reads an environment variable changes without publishing the file:

Environment variableSettingDefault
PRODUCTION_PREFLIGHT_ENV_FILEproduction-preflight.env.filethe .env in the base path
PRODUCTION_PREFLIGHT_ENV_EXAMPLEproduction-preflight.env.examplethe .env.example in the base path
PRODUCTION_PREFLIGHT_HTTP_PROBEproduction-preflight.http_probe.enabledoff
PRODUCTION_PREFLIGHT_HTTP_PROBE_TIMEOUTproduction-preflight.http_probe.timeout5 seconds
PRODUCTION_PREFLIGHT_PHP_FPMproduction-preflight.php.fpmnone
PRODUCTION_PREFLIGHT_PHP_TIMEOUTproduction-preflight.php.timeout10 seconds

The probe switch is on only for true, 1, yes and on, in any case. A timeout that is no number, or one below 1, keeps its default.

Stages​

The production rules apply in production and on staging. A staging installation often holds real customer data, and a check that listened to production alone would stay quiet exactly there. The stage is read from app.env, against the names in production-preflight.stages:

'stages' => [
'production' => ['production', 'prod', 'live'],
'staging' => ['staging', 'stage', 'preprod', 'uat'],
'development' => ['local', 'testing', 'test', 'dev', 'development', 'ci'],
],

A name on none of the lists is undetermined, never development, so a server that declares a name nobody mapped is not taken for a laptop. Add the name to the right list.

Acknowledging a check​

Some findings are decisions. QUEUE_CONNECTION=sync on an installation one person uses is one. Acknowledge such a check by its key, with the reason:

'acknowledged' => [
'queue-connection' => 'One person uses this installation; jobs run in the request on purpose.',
],

An acknowledged check passes, and its summary names the reason and what was found, in the console, in the result laravel-health stores, and in every notification. The decision stays visible instead of turning into silence. An acknowledgement also covers a check that could not look, such as the file checks on a platform that sets the environment without a .env.

Commands​

CommandRunsExit codes
php artisan preflight --deploythe file and configuration checks, and records the deploy0 nothing failed, 1 a check failed, 2 a check could not look
php artisan preflightevery checkthe same
php artisan env:lint [file]the file checks, against .env or the file named0 clean, 1 any finding, warnings included, 2 nothing could be checked

env:lint is the strict one: two lines with equal values are harmless today and one edit away from a setting that does not apply. --quiet-when-clean prints nothing when nothing was found, for a schedule.

Both commands print values only with --show-values, which --deploy refuses: a deploy keeps its output. What reaches which channel explains why.