softwax/health-check-bundle
A health check bundle for PHP applications that adds ready-to-use endpoints for monitoring service status. Define custom checks for databases, caches, and external services, then expose results for load balancers, uptime monitors, and container orchestrators.
1.0.0 is the first stable release of softwax/health-check-bundle, and the release notes are effectively empty (just a changelog link). Treat the README and the 1.0.0 diff as the real documentation — read them before wiring anything into production probes.
Minimal path:
composer require softwax/health-check-bundle
bootstrap/providers.php / config/app.php.php artisan vendor:publish --tag=... — confirm the exact tag in the README). This is where enabled checks, route path, middleware and output format live./health by convention) and confirm a 200 with a JSON body listing each check.First use case: point your uptime monitor (Better Stack, UptimeRobot, Pingdom) at the public endpoint and your orchestrator's readiness probe at the same URL. Don't start by adding ten checks — start with the two that actually page you: database connectivity and cache/queue reachability.
Where to look first, in order: published config → the check interface/abstract class → the route registration → the response formatter.
Split liveness from readiness. The single most important pattern: a liveness endpoint that returns 200 as long as PHP is running (no DB, no Redis, no HTTP calls), and a readiness endpoint that runs the real dependency checks. Kubernetes/Rolling deploys that point liveness at a DB check will crash-loop your pods during a database blip. If the package ships one route, register a second group with an empty/minimal check set.
Custom checks. For anything domain-specific (a "is the last import job fresh?" check, a Stripe reachability check), implement the package's check contract rather than shelling out in a route closure. This keeps checks discoverable, individually testable, and included in the package's JSON output automatically. Register them in a service provider, not in config, so they survive config:cache.
Group checks by criticality. Tag checks as critical (fails the whole probe with 503) versus advisory (reported in the body but returns 200). A stale search index should show up in your dashboard without pulling healthy instances out of the load balancer.
Protect detail, expose status. Public probes should return a bare {"status":"ok"} or 503. The per-check breakdown — versions, connection targets, exception messages — belongs behind auth or an internal-only route. Most of these bundles let you configure a "verbose" route separately; use it for humans, not for the load balancer.
Gate deploys on it. After php artisan migrate, call the readiness endpoint (or the package's artisan command, if it exposes one) in your deploy script and fail the release on 503. This catches the classic "env var missing in the new environment" class of failure before traffic shifts.
Cache expensive checks. If a check takes >100ms (external API, queue depth query), wrap it in a short-lived cache (5–15s) inside the check itself. Probes fire every few seconds per instance; an uncached external call turns your monitoring into a load generator.
timeoutSeconds.503 vs 200-with-body. Some teams return 200 with "status":"degraded"; load balancers and k8s only understand the status code. Pick per-endpoint deliberately, and document it.config:cache trap. Anything read via env() at request time returns null once config is cached. Config values only. Custom checks registered dynamically at runtime (e.g. from a loop) won't be reflected in the cached route/config — register them statically.auth middleware before this reaches a public URL.^1.0 and read the changelog on each minor. The check contract and the JSON schema are the two things most likely to shift — if you're writing a dashboard against the response body, write a small adapter layer so a schema change is a one-file fix.app(MyCheck::class)->run()) or via php artisan tinker before suspecting the bundle. The package is usually reporting faithfully; the failure is in the check's assumptions (wrong connection name, missing table, no storage write permission).How can I help you explore Laravel packages today?