Doctor
php artisan compose:doctor
php artisan compose:doctor --no-daemon # skip everything that needs docker
The doctor is a preflight. Every check is something that broke a real deploy once. It exits 1 on any error and 0 otherwise; warnings are printed but don't fail it.
Host checks
- The docker binary runs (the configured
docker.binary,dockerby default). - The Compose plugin is installed and is 2.17 or newer. The redeploy needs
pull --ignore-buildable(added in 2.15) andup --wait-timeout(2.17). - The daemon answers. A
permission deniedbecomes a hint naming the user and theusermod -aG dockercommand; anything else is printed as Docker reported it. With a remote host or context, the address reached is printed.
Per-stack checks
- The compose file parses.
- A
Stackclass found in the directory can be autoloaded. When it can't, the stack silently runs class-less, and the warning names the class. environment()renders: valid keys, no line breaks, supported value types.- Every
${VAR}the compose file consumes without a default is a key ofenvironment(). A stack with nothing to write (a class-less one) is checked against the env file already in its directory instead, the one a redeploy leaves alone.${COMPOSE_PROJECT_NAME}always counts as provided. A variable that only the shell provides (${HOME}, a CI secret) is a warning rather than an error: compose will resolve it on this machine, but the stack doesn't control it. docker compose configaccepts the file with the environment the stack would write. The env is passed through a temporary file, so the stack directory isn't touched. A stack with nothing to write is checked with its own env file.- No publish listens on every interface. Addresses given as
${BIND:-...}are resolved through the stack's environment first (or the hand-written env file, for a stack with nothing to write), so a variable that resolves to127.0.0.1passes. - For remote daemons, no bind mounts.
- For local daemons with a link directory, the link can be created or refreshed by this user. When it can't, the warning names the directory that needs
chownand the user to give it to. A directory with content sitting at the link path gets its own warning, since the redeploy leaves it alone; so does one this user can't read. - The env file would be ignored by git, when the stack lives in a repository.
- Disabled stacks and a disabled master switch are noted, so "why didn't it deploy" has an answer.
In your test suite
The same checks are available as a report, which makes a good architecture test: it catches a compose file that consumes a renamed variable before the branch merges.
use Mozex\Compose\Facades\Compose;
it('keeps every stack consistent with its compose file', function (): void {
$report = Compose::validate(withDaemon: false);
expect($report->isClean())->toBeTrue(implode("\n", $report->lines()));
});
Report exposes errors(), warnings(), notes(), forStack($name), isClean(), hasWarnings(), and lines(), which is every error and warning as one string each for an assertion message. Each Problem has a severity, a message, and the stack it belongs to, or null for host-level findings.