nowo-tech / health-kit-bundle
Liveness and readiness endpoints with pluggable probes (Doctrine, Redis, Messenger queue depth, FrankenPHP) and a token-guarded Prometheus /metrics endpoint for Symfony apps.
Package info
github.com/nowo-tech/HealthKitBundle
Type:symfony-bundle
pkg:composer/nowo-tech/health-kit-bundle
Requires
- php: >=8.2 <8.6
- psr/cache: ^2.0 || ^3.0
- psr/clock: ^1.0
- psr/log: ^2.0 || ^3.0
- symfony/clock: ^7.4 || ^8.0
- symfony/config: ^7.4 || ^8.0
- symfony/dependency-injection: ^7.4 || ^8.0
- symfony/http-foundation: ^7.4 || ^8.0
- symfony/http-kernel: ^7.4 || ^8.0
- symfony/polyfill-mbstring: ^1.31
- symfony/routing: ^7.4 || ^8.0
Requires (Dev)
- doctrine/dbal: ^3.8 || ^4.0
- doctrine/doctrine-bundle: ^2.13 || ^3.0
- friendsofphp/php-cs-fixer: ^3.0
- igor-php/igor-php: ^0.10.1
- nowo-tech/phpstan-frankenphp: ^1.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.5
- predis/predis: ^2.2 || ^3.0
- rector/rector: ^2.0
- symfony/cache: ^7.4 || ^8.0
- symfony/framework-bundle: ^7.4 || ^8.0
- symfony/messenger: ^7.4 || ^8.0
- symfony/phpunit-bridge: ^7.4 || ^8.0
- symfony/security-core: ^7.4 || ^8.0
- symfony/yaml: ^7.4 || ^8.0
Suggests
- doctrine/doctrine-bundle: Enables the doctrine.<connection> readiness probes (probes.doctrine)
- predis/predis: Redis client usable by the redis probe (DSN or client service) when ext-redis / ext-relay are not installed
- symfony/cache: Opens Redis connections from a DSN for the redis probe (probes.redis.dsn); not needed with probes.redis.client
- symfony/flex: Installs the recipe (config/packages/nowo_health_kit.yaml and the health route import)
- symfony/messenger: Enables the messenger.<transport> queue-depth probes (probes.messenger)
- symfony/security-core: Lets session roles scrape /metrics (metrics.roles)
Provides
None
Conflicts
None
Replaces
None
README
⭐ Found this useful? Install from Packagist · Give it a star on GitHub so more developers can find it.
Health Kit Bundle — /health/live and /health/ready endpoints for orchestrators and load balancers, with pluggable probes (Doctrine DBAL per connection, Redis, Messenger queue depth with thresholds, FrankenPHP runtime, and your own tagged probes), per-probe timeouts, details only for allowlisted IPs or a bearer token, and a token-guarded Prometheus /metrics endpoint that aggregates your metrics providers. Supports Symfony 7.4, 8.0 and 8.1 (CI: 7.4 and 8.1; 8.0 is end-of-life) · PHP 8.2+ (Symfony 8.x requires PHP 8.4+).
This bundle is FrankenPHP worker mode friendly.
Workers that do not reset the kernel between requests are supported too: probes open and close their own connections, the only kept state is a per-worker memo of the anonymous readiness status. See docs/FRANKENPHP-WORKER-AUDIT.md.
Screenshots
Readiness probes in detail (token caller) vs the status-only public answer |
Token-guarded Prometheus scrape: probe gauges + host providers |
Features
/health/liveand/health/ready— JSON,Cache-Control: no-store, private,GET/HEAD, stateless routes. HTTP 200 when every critical probe passes (okordegraded), 503 otherwise.- Opt-in routes — nothing is exposed until you import
@NowoHealthKitBundle/config/routes/health.yaml(andmetrics.yaml); the controllers are never picked up byrouting.controllers. - Status-only by default — anonymous callers get
{"status": "ok"}. Probe names, durations, messages and data go only to callers whoseREMOTE_ADDRis indetails.allowed_ips(IPs/CIDRs, loopback by default; ignored for requests carrying forwarding headers — mind same-host reverse proxies, see CONFIGURATION) or who sendAuthorization: Bearer <details.token>(constant-time compare, never logged,?token=ignored). - Built-in probes —
doctrine.<connection>(SELECT 1on each DBAL connection),redis(PINGthrough a client service or a DSN with connect/read timeouts),messenger.<transport>(queue depth vswarn_at/fail_at),frankenphp(classic/worker mode, worker number, kernel reset; optionalrequire_worker). - Your own probes — implement
HealthProbeInterface, optionally with#[AsHealthProbe(name:, groups:, critical:, timeout:)]; autoconfigured. - Timeouts and budgets — every probe gets its timeout (to bound its I/O) and is reported as an error when it overruns; a group budget stops starting probes once spent. Non-critical probes only degrade the answer.
- Cheap public readiness — the anonymous answer is reused for
ready.public_cache_ttlseconds (default 5): per-worker memo plus a shared PSR-6 pool (ready.cache_pool, defaultcache.app), so PHP-FPM and classic mode do not run the probes on every anonymous request either. - Prometheus
/metrics— text format 0.0.4,Authorization: Bearer <metrics.token>or a configured role; fail-closed (404 when disabled, 503 when no access is configured, 401 otherwise). Aggregates everyMetricsProviderInterfaceservice (autoconfigured) and, by default, exports each probe ashealth_probe_status,health_probe_duration_secondsandhealth_probe_datagauges. - Runtime tokens —
details.token_provider/metrics.token_providerpoint at a service that returns the token per request (tokens kept in editable settings), with the same fail-closed rules. - Reusable formatter —
PrometheusTextFormatterwith validated metric/label names and escaped help/label values.
Installation
composer require nowo-tech/health-kit-bundle
With Symfony Flex the recipe adds config/packages/nowo_health_kit.yaml and imports the health routes. Without Flex, register the bundle and import the routes yourself — see docs/INSTALLATION.md.
# config/routes/nowo_health_kit.yaml nowo_health_kit_health: resource: '@NowoHealthKitBundle/config/routes/health.yaml' nowo_health_kit_metrics: # only if you scrape /metrics resource: '@NowoHealthKitBundle/config/routes/metrics.yaml'
Let the endpoints through your firewall (access_control: - { path: ^/(health/(live|ready)|metrics)$, roles: PUBLIC_ACCESS }): the bundle does its own access checks.
Quick start
# config/packages/nowo_health_kit.yaml nowo_health_kit: version: '%env(default::APP_VERSION)%' details: token: '%env(default::HEALTH_DETAILS_TOKEN)%' # >= 16 chars; loopback callers see details anyway probes: doctrine: { enabled: true } redis: { enabled: true, dsn: '%env(REDIS_URL)%' } messenger: enabled: true transports: async: { warn_at: 100, fail_at: 1000 } metrics: enabled: true token: '%env(METRICS_TOKEN)%'
curl -s https://app.example/health/ready # {"status":"ok"} curl -s -H "Authorization: Bearer $HEALTH_DETAILS_TOKEN" https://app.example/health/ready # + checks curl -s -H "Authorization: Bearer $METRICS_TOKEN" https://app.example/metrics
Requirements
- PHP 8.2+ (
>=8.2 <8.6; Symfony 8.x requires PHP 8.4+) - Symfony 7.4, 8.0 or 8.1 (
symfony/http-kernel,routing,dependency-injection,config,http-foundation,clock) - Optional:
doctrine/doctrine-bundle(Doctrine probes),symfony/cache(Redis DSN probe),symfony/messenger(queue probes),symfony/security-core(metrics.roles)
Configuration
Every key, its default and its validation are documented in docs/CONFIGURATION.md. Safe defaults: details for loopback only, no token, only the FrankenPHP probe on, /metrics disabled.
Usage
Custom probes, custom metrics providers, Kubernetes / Docker healthchecks, Prometheus scrape config and the JSON contract: docs/USAGE.md.
use Nowo\HealthKitBundle\Attribute\AsHealthProbe; use Nowo\HealthKitBundle\Probe\HealthProbeInterface; use Nowo\HealthKitBundle\Probe\ProbeResult; #[AsHealthProbe(name: 'payments_api', groups: ['ready'], critical: false, timeout: 1.5)] final class PaymentsApiProbe implements HealthProbeInterface { public function __construct(private PaymentsClient $client) {} public function check(float $timeout): ProbeResult { return $this->client->ping(timeout: $timeout) ? ProbeResult::ok() : ProbeResult::error('Payments API unreachable.'); } }
Demo
demo/symfony8— Symfony 8.1 (FrankenPHP PHP 8.5, worker mode) with MySQL, Redis and a Doctrine Messenger queue; host port 8197 by default.
The demo page calls the real endpoints through sub-requests (public caller, token caller, scraper) and shows their output. Runtime mode is FRANKENPHP_MODE (worker default, or classic) — see docs/DEMO-FRANKENPHP.md.
make -C demo up-symfony8 # http://localhost:8197 make test-e2e # Playwright e2e against the running demo make demo-screenshots # refresh docs/images/demo/*.webp make demo-smoke # boot, HTTP 200, down (REQ-TEST-011)
Development
make up
make install
make test
make cs-check
make phpstan
make igor
make test-coverage-100
make release-check
Documentation
- Installation
- Configuration
- Usage
- Contributing
- Code of Conduct
- Changelog
- Upgrading
- Release
- Security
- Engram
- Spec-driven development
- GitHub Spec Kit
Additional documentation
- Architecture
- PSR evaluation (REQ-CS-007)
- Demo (FrankenPHP)
- FrankenPHP worker audit (kernel not reset)
- GitHub Actions CI requirements
Tests and coverage
- Tests: PHPUnit (PHP) — unit tests plus kernel integration tests (endpoints, opt-in routes, invalid configuration); Playwright e2e on the demo
- PHP: 100% (761/761 statements, 852/852 elements; gate enforced in CI by
composer test-coverage-100, no exclusions) - TS/JS: N/A
- Python: N/A
License and author
MIT · Nowo.tech · Héctor Franco Aceituno


