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 Bundle Laravel Package

mainlycode/health-bundle

Health Bundle adds configurable health-check endpoints to PHP applications, letting monitoring tools verify application, database, cache, and dependency status through simple, extensible checks.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package. Before anything else, decide which of two jobs you're doing:

  1. You maintain a Symfony service that needs a /health endpoint → install it directly.
  2. You're a Laravel developer evaluating it as a reference implementation for your own GET /health route → read src/ for the check-collection pattern and port it (see Implementation Patterns for the mapping).

The package has 0 stars, 0 dependents, and a single maintainer. That doesn't make it bad — a health endpoint is ~200 lines of glue — but it does mean you should read the source, not just the README, and be prepared to fork (MIT, so trivial).

Minimal install (Symfony):

composer require mainlycode/health-bundle

If Flex doesn't auto-register it, add to config/bundles.php:

MainlyCode\HealthBundle\MainlyCodeHealthBundle::class => ['all' => true],

Then verify what actually got wired up — this is your first debugging step and answers 80% of questions:

bin/console debug:router | grep -i health
bin/console debug:container --tag=<the check tag you find in src/>
curl -i http://localhost:8000/health

Where to look first, in order:

Path Why
src/Resources/config/ (or config/) The service definitions — this tells you the real tag name, route path, and default status codes
src/DependencyInjection/ The compiler pass; how checks are collected and what config keys exist
src/Controller/ or src/Health/ The endpoint + the check interface you'll implement
README.md Often lags the code in small bundles — treat it as a hint

First use case: don't build a custom check yet. Get the bundled endpoint responding 200, then add one check for the dependency that actually pages you — usually the database. A health endpoint that reports on twelve things is noise; one that reports on the database and the queue is signal.

Implementation Patterns

The core pattern: tagged checks

The bundle almost certainly revolves around a check interface plus a DI tag. The shape you should expect:

namespace App\Health;

use MainlyCode\HealthBundle\Health\HealthCheckInterface; // confirm FQCN in src/

final class DatabaseCheck implements HealthCheckInterface
{
    public function __construct(private \PDO $pdo) {}

    public function check(): bool // or a Result/HealthStatus object
    {
        return (bool) $this->pdo->query('SELECT 1');
    }
}

Autoconfigure it so you never hand-write tag names in services.yaml:

# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\Health\:
        resource: '../src/Health/'

If the bundle declares autoconfigure on its interface, that's enough. If not, add _instanceof with the exact tag from Resources/config:

    _instanceof:
        MainlyCode\HealthBundle\Health\HealthCheckInterface:
            tags: ['mainlycode.health.check']

Use autoconfigure + _instanceof in preference to per-service tags: tag typos fail silently — a check that's never collected just disappears from the response and you'll debug the checker instead of the wiring.

Liveness vs readiness

Most bundles of this size expose one route. If you're on Kubernetes or behind an ALB, one route is a trap: a database blip will fail the endpoint, the orchestrator will conclude the process is broken, and restart every replica — turning a slow query into a full outage.

Two viable approaches with a single-route bundle:

  • Query parameter or header switch: /health?deep=1 for readiness, plain /health returns {"status":"ok"} unconditionally. Cheap on the kubelet's 10-second probe budget.
  • Two app-level routes: a trivial /live controller that returns 200 always, plus the bundle's /health reserved for readiness and your load balancer.

Prefer the second. Liveness should never touch the database.

Response contract

Whatever the bundle returns by default, pin it down and make it a contract:

{ "status": "ok", "checks": { "database": "ok", "redis": "ok" }, "duration_ms": 4 }
  • 200 on healthy, 503 on degraded — orchestrators and ALBs read the status code, humans read the body.
  • Never let a failing check throw out of the controller; a 500 with a stack trace is worse than a 503 with {"status":"fail"}.
  • HEAD should work. Some probes send it.

Integration tips

  • Exclude the route from auth, CSRF, and rate limiting. In Symfony that's security.firewalls.main.security: false via access_control, or a #[IsGranted]-free path. A 302 to a login page will be read as a failure.
  • Budget the whole endpoint. Give each check a hard timeout (PDO ATTR_TIMEOUT, Redis OPT_TIMEOUT) so a hung dependency fails in ~1s rather than the kubelet's timeout, which gives you an unhelpful "connection reset".
  • Cache expensive checks. A replica set ping every 10 seconds from every pod is fine; a full external API call is not. Wrap in a 15–30s cache if needed.
  • Silence it. Health endpoints should not write access logs at the same volume as real traffic, and should not go to Sentry. In Symfony, exclude the path from MonologBundle's handlers or your log volume quietly doubles.
  • Smoke-test it in CI: curl -fsS http://localhost:8000/health || (docker compose logs; exit 1) after bringing up the stack catches wiring regressions before deploy.

If you're porting to Laravel

The concepts map almost 1:1:

Symfony bundle Laravel equivalent
Bundle + Flex recipe Package + ServiceProvider
Service tag + compiler pass $this->app->tag([...], 'health') + $this->app->tagged('health')
Resources/config/*.yaml config/health.php + mergeConfigFrom
Route in bundle config routes/api.php or bootstrap/app.php
Check interface interface HealthCheck { public function check(): bool; }
200/503 response()->json([...], $ok ? 200 : 503)

Laravel 11+ already ships a trivial /up route (->withRouting(health: '/up') in bootstrap/app.php) — that's your liveness probe. For readiness, either port this bundle's tagged-check pattern into a small HealthController or use spatie/laravel-health, which is the mature ecosystem answer and is worth preferring over hand-rolling unless you specifically want zero dependencies.

The port is small enough that copying the idea is usually better than shimming a Symfony bundle into Laravel — they don't compose.

Gotchas and Tips

  • The bundle is nearly abandoned by the numbers. Zero stars, zero dependents, one maintainer, a future-dated "last release." Pin it exactly ("mainlycode/health-bundle": "1.0.*"), commit the lockfile, and assume you may own the code. Because it's MIT, vendor it into src/ or fork it the moment you need a change you can't upstream — the whole thing is a controller, an interface, and a compiler pass.
  • Confirm the interface FQCN before writing checks. Small Symfony bundles rename things between 0.x and 1.x, and class names in the README are frequently stale. grep -r "interface .*Check" vendor/mainlycode/health-bundle/src/ takes ten seconds and saves an afternoon.
  • Tag typos are invisible. A check that isn't tagged simply never runs, and the endpoint happily returns 200. Verify collection explicitly: bin/console debug:container --tag=<tag> should list your check. If it's empty, the problem is DI configuration, not your check logic.
  • /health collides more often than you'd think — existing monitoring routes, API gateways, and reverse proxies all claim it. Check debug:router for a duplicate and confirm route ordering; a controller route defined in your app will usually win over one imported from a bundle, which is a silent way to keep returning a hardcoded 200.
  • Don't leak internals. A failure body containing SQLSTATE[08006] ... host=db-prod-1 is a gift to anyone scanning. Return a check name and a status, log the exception server-side. Also confirm whether the endpoint is publicly reachable from the internet — it usually shouldn't be.
  • Kubelet probes are aggressive. Default period is 10s with failureThreshold: 3. If a check can take longer than ~5s under load, set timeoutSeconds and failureThreshold deliberately, or you'll get restart loops on the first slow query.
  • kernel.debug may change behavior — some bundles return verbose output only in debug. Test the endpoint with APP_ENV=prod at least once, or you'll ship a response shape you never actually validated.
  • Extension points, in order of usefulness: (1) the check interface — implement your own; (2) the DI tag to control ordering/priority; (3) if the controller builds the response inline, decorate it rather than editing the bundle (#[AsDecorator] on the controller service) so upgrades stay clean; (4) the config tree for path/status codes if it exposes them.
  • Write one functional test. KernelBrowser hitting /health, plus a check stub swapped in via the test container that returns false, asserting 503. That test is the difference between trusting the endpoint in production and hoping.
  • If you only take one thing from this package, take the tagged-check pattern. It's the right abstraction regardless of framework, and it's small enough to reimplement in Laravel or plain PHP in under an hour without inheriting the maintenance risk.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums