In operation
The checks are laravel-health checks. Registered with it, they run with everything else the application monitors, their results land in its store, and a failing one is notified the way the application configured.
Registering the checks
In the boot method of a service provider:
use Pushery\ProductionPreflight\Preflight;
use Spatie\Health\Checks\Checks\OptimizedAppCheck;
use Spatie\Health\Checks\Checks\ScheduleCheck;
use Spatie\Health\Facades\Health;
Health::checks([
...Preflight::checks(),
ScheduleCheck::new(),
OptimizedAppCheck::new(),
]);
Preflight::checks() returns every check of this package; Preflight::checks(Scope::Host) returns one group. ScheduleCheck and OptimizedAppCheck come from laravel-health and cover what this package leaves to it: a scheduler that stopped, and a configuration, routes or events that are not cached.
laravel-health runs the checks with health:check. Schedule it, together with the heartbeat ScheduleCheck reads:
// routes/console.php
use Illuminate\Support\Facades\Schedule;
use Spatie\Health\Commands\RunHealthChecksCommand;
use Spatie\Health\Commands\ScheduleCheckHeartbeatCommand;
Schedule::command(RunHealthChecksCommand::class)->everyMinute();
Schedule::command(ScheduleCheckHeartbeatCommand::class)->everyMinute();
A check that could not look reports as skipped. A check that throws, a database it cannot reach for instance, laravel-health reports as crashed. Neither reads as a pass.
Checks that need more than the defaults
Three checks of the installation cannot answer from the command line on their own, and none of them guesses. Each says it did not look until it can.
Whether the site serves .env
Whether a web server hands out .env or .git/config depends on its configuration, which no file of the application describes. The env-over-http check asks the site itself, with a request to app.url, once switched on:
PRODUCTION_PREFLIGHT_HTTP_PROBE=true
PRODUCTION_PREFLIGHT_HTTP_PROBE_TIMEOUT=5
A file counts as served when the answer is 200 and the body looks like the file. A site that answers every path with its start page, as a single-page application does, therefore passes.
The PHP version of the web server
Migrations and scheduled commands run under the command line's PHP; the application under the web server's. The php-version-parity check runs the web server's binary with -v and compares the minor versions. Name the binary:
PRODUCTION_PREFLIGHT_PHP_FPM=/usr/sbin/php-fpm8.4
On a host that switched off proc_open, the check says it could not look.
OPcache
The command line has OPcache settings of its own, so the opcache check answers only in a web request. laravel-health's Oh Dear endpoint runs the checks in one when always_send_fresh_results is on, which is its default.
Telling the application's own monitoring
Both commands dispatch Pushery\ProductionPreflight\Events\PreflightCompleted when they finish, with the command, the exit code and laravel-health's result for each check. A listener forwards it wherever the application reports its scheduled runs, so the alert names the keys instead of carrying an exit code alone:
use Illuminate\Support\Facades\Event;
use Pushery\ProductionPreflight\Events\PreflightCompleted;
Event::listen(function (PreflightCompleted $event): void {
if ($event->exitCode !== 0) {
report(new RuntimeException("{$event->command} found: ".$event->message()));
}
});
The results carry the summary and the findings of each check, never a value: they are built for channels that keep what they are given. What reaches which channel explains the line.
Linting the .env on a schedule
env:lint reads the file alone, which makes it cheap enough for a daily run of its own, silent unless it finds something:
Schedule::command('env:lint --quiet-when-clean')->daily();