Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Health Check Bundle Laravel Package

silpo-tech/health-check-bundle

A health check bundle for Laravel apps that adds ready-made health endpoints and pluggable checks for database, cache, queue, disk and custom services, so uptime monitors and load balancers can confirm your application and its dependencies are up.

View on GitHub
Deep Wiki
Context7

Getting Started

First, confirm what you're actually adopting. Despite the "Bundle" name and Symfony target, the package is installable via Composer and its useful parts are usually plain PHP classes. Before writing any integration code, read these files in order:

  1. composer.json — note the "type" (symfony-bundle means it expects a Symfony kernel), the require block (look for symfony/http-kernel, symfony/dependency-injection, symfony/config), and the minimum PHP version.
  2. README.md — the reference wiring is almost always a YAML service definition plus a tagged service list. Read it as documentation of the design, not as install instructions.
  3. DependencyInjection/ (or src/DependencyInjection/) — the Extension and Configuration classes tell you every config key and default. This is the real API surface.
  4. The checker/orchestrator classes and the controller or console command that exposes them — this is where the HTTP status mapping and response shape live.

Minimal path in a Laravel app. You are almost certainly not going to boot the bundle. The pragmatic route:

composer require silpo-tech/health-check-bundle

Then register only the classes you need in a service provider, bypassing the bundle's Extension:

// app/Providers/HealthServiceProvider.php
public function register(): void
{
    $this->app->singleton(\Silpo\HealthCheck\HealthChecker::class, function ($app) {
        return new \Silpo\HealthCheck\HealthChecker(
            $app->tagged('health.checks') // or an explicit array
        );
    });
}

First use case: a readiness endpoint for your load balancer. Expose GET /health returning 200 when all checks pass and 503 when any fail, with a JSON body listing per-check status. Wire it in routes/api.php outside the auth and throttle middleware groups:

Route::get('/health', fn (\Silpo\HealthCheck\HealthChecker $h) =>
    response()->json($h->run(), $h->isHealthy() ? 200 : 503)
)->withoutMiddleware(['auth']);

Validate the shape of $h->run() before you commit to it in k8s probes.

Implementation Patterns

Check-as-service with a tag. The dominant Symfony pattern is: each check is a class implementing a single interface with one method (typically check(): Result), registered with a DI tag, and the orchestrator receives them as a tagged iterator. Reproduce it in Laravel with a tag name and a small registrar:

// AppServiceProvider::register()
$this->app->singleton(HealthChecker::class, fn ($app) => new HealthChecker(
    collect($app->tagged('health.checks'))->all()
));

Then tag in a dedicated provider so checks stay discoverable:

$this->app->tag([DatabaseCheck::class, RedisCheck::class, DiskSpaceCheck::class], 'health.checks');

Split liveness from readiness. One endpoint doing everything is the classic mistake — a slow dependency drags down liveness and your orchestrator restarts healthy pods. Pattern:

  • /health/live — process-only, no I/O, always fast, returns 200 unless the app can't serve at all.
  • /health/ready — full dependency set (DB, cache, queue broker), 503 if any critical check fails.
  • /health/deep — everything, unauthenticated only on the private network, used by humans and dashboards.

If the bundle only ships one aggregate entry point, wrap it twice with different check subsets rather than forking the package.

Tier your checks by criticality. Tag checks as critical (fail the request) versus degraded (report in the payload, still return 200). Otherwise a full disk on a logging volume takes your whole fleet out of rotation.

Cache expensive results. A DB ping per probe at a 2-second interval across 40 pods is real load. Wrap slow checks in a short-lived cache (Cache::remember('health.db', 5, ...)) and keep the probe interval longer than the TTL. Note that a cached check also caches transient blips — pick the TTL deliberately.

Console parity. Add an artisan command that runs the same checker set so operators can reproduce a failing probe without hitting HTTP, and so CI can smoke-test dependencies after a deploy:

Artisan::command('health:check', function () {
    $results = app(HealthChecker::class)->run();
    // print table, return non-zero on critical failure
});

Bridge rather than port. If the bundle's checkers depend on Symfony's HttpKernel or DependencyInjection in their constructors, do not try to fake a container. Write thin Laravel-native adapters implementing the package's check interface and delegate the actual probing (PDO, Redis::ping(), disk_free_space()) to Laravel's own facilities. You keep the orchestration and response semantics, which is 90% of the value, and drop the framework coupling.

Response contract. Decide the payload once and freeze it: { "status": "ok|degraded|fail", "checks": { "name": {"status": "...", "duration_ms": ..., "message": "..." } } }. Dashboards and alerting rules will encode this shape, so changing it later is a breaking change.

Gotchas and Tips

Zero dependents is the headline risk. No stars, no dependents, no ecosystem means no one else has hit the edge cases for you and no third party is watching for supply-chain issues. Pin an exact commit hash, and vendor-review the checker implementations — health endpoints touch credentials and internal hostnames, and they are frequently the least-reviewed code in a codebase. Read every checker before it ships.

It is not a Laravel package. There is no service provider, no facade, no config/health.php, and php artisan vendor:publish will do nothing. The Extension class will never run, so config defaults from the bundle do not apply to you — you must replicate any defaults yourself or you'll silently get nulls where you expected sensible values. This is the single most common source of "the check works but returns the wrong thing."

Tag names are strings and typos fail silently. $app->tagged('health.checks') returns an empty array for an unknown tag — no exception. A misspelled tag means your health endpoint reports healthy because it ran zero checks. Assert the check count in a test.

config:cache interactions. If you register checks conditionally based on env() at boot, that logic is evaluated once at cache time. Use config() values populated from config/*.php instead of raw env() calls in the provider, or your staging/prod check sets will diverge unexpectedly.

Never return 500 from a health endpoint. A 500 means "the app is broken"; a 503 means "the app is up but not ready." Proxies and orchestrators treat these differently, and conflating them produces false incident pages. Also catch Throwable per-check — one uncaught exception must not blank the entire payload.

Beware the self-inflicted outage. A health check that opens a database connection and doesn't close it, or runs a SELECT on a large table, will, at scale, become a load source. Prefer PDO::ATTR_TIMEOUT-bounded pings and hard timeouts on every network check. A check with no timeout is a hang waiting to happen.

Auth middleware is the classic probe failure. Laravel's default web group carries CSRF and session middleware; your probe will 419 or 302 and your orchestrator will read that as unhealthy. Register health routes in a dedicated file or explicitly ->withoutMiddleware().

Do not leak internals in the response body. Exception messages from a DB check frequently contain hostnames, usernames, and ports. On public-facing endpoints, return status codes only and log the detail; keep verbose payloads for the internal /health/deep route.

Version-pin the Symfony components. If you pull in the bundle's transitive symfony/* dependencies, you're now resolving constraints against a framework you don't run, and a composer update can drag in a major version bump. Add explicit constraints on symfony/http-kernel and symfony/dependency-injection to the highest version you've actually tested, or use replace/provide if you've stripped those imports out.

Extension points to prefer. When you need to change behaviour, the stable seams are (a) implementing the check interface for new probes, (b) subclassing or wrapping the orchestrator for different aggregation policies, and (c) your own controller for response formatting. Do not patch the bundle in vendor/ — with no upstream activity, your patch will be lost on the next composer install, and there is no maintainer to upstream it to. Fork it under your org if you need real changes, and record why.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core