Containers die lijken te draaien maar eigenlijk vastgelopen zijn, ontdekte ik vorig jaar toen een Laravel-applicatie urenlang onbereikbaar bleef terwijl de container status nog steeds 'running' toonde. Het probleem lag bij een MySQL-connectie die was vastgelopen, maar Docker had geen idee dat de applicatie niet meer functioneerde. Health checks lossen dit soort situaties op door containers continu te monitoren en automatisch opnieuw op te starten wanneer ze niet meer reageren.
Docker health checks zijn commando's die Docker periodiek uitvoert om te controleren of een container correct functioneert. Wanneer een health check faalt, markeert Docker de container als 'unhealthy' en kan orchestratie-tools zoals Docker Swarm of Kubernetes automatisch actie ondernemen. Voor Laravel-applicaties betekent dit dat ik niet alleen controleer of de container draait, maar ook of de applicatie daadwerkelijk HTTP-requests kan verwerken en verbinding heeft met de database.
Health check strategie voor Laravel containers
Mijn aanpak voor Laravel health checks bestaat uit meerdere lagen: eerst controleer ik of de webserver reageert, daarna test ik of Laravel zelf functioneert, en ten slotte verifieer ik of de database-connectie werkt. Deze gelaagde controle geeft een compleet beeld van de applicatie-status.
De basis health check begint met een simpele HTTP-request naar een dedicated endpoint. Ik maak altijd een route specifiek voor health checks die geen authenticatie vereist en minimale resources gebruikt:
// routes/web.php
Route::get('/health', function () {
return response()->json([
'status' => 'healthy',
'timestamp' => now(),
'app_name' => config('app.name')
]);
});
Deze route test of PHP-FPM en de webserver correct samenwerken. Maar dat is nog niet genoeg voor een productie-omgeving waar database-connecties cruciaal zijn. Daarom bouw ik de health check uit met een database-verbindingstest:
// routes/web.php
Route::get('/health', function () {
$checks = [
'status' => 'healthy',
'timestamp' => now(),
'app_name' => config('app.name')
];
try {
DB::connection()->getPdo();
$checks['database'] = 'connected';
} catch (Exception $e) {
$checks['database'] = 'failed';
$checks['status'] = 'unhealthy';
return response()->json($checks, 503);
}
return response()->json($checks);
});
Door een 503 status code terug te geven bij database-problemen, kan Docker onderscheid maken tussen een tijdelijk probleem en een echte failure. Status codes boven 400 markeren de health check als gefaald.
Docker Compose configuratie met afhankelijkheden
Health checks configureer ik direct in de Docker Compose file, waarbij ik rekening houd met startup-tijden en database-afhankelijkheden. Laravel containers hebben tijd nodig om op te starten, vooral als ze wachten op MySQL-verbindingen:
version: '3.8'
services:
app:
build: .
depends_on:
mysql:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-psecret"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
De start_period parameter is cruciaal omdat Laravel containers tijd nodig hebben om composer dependencies te laden en verbinding te maken met MySQL. Tijdens deze periode tellen gefaalde health checks niet mee, wat voorkomt dat containers voortijdig worden afgesloten tijdens het opstarten.
Het depends_on blok met condition: service_healthy zorgt ervoor dat de Laravel container pas start wanneer MySQL volledig operationeel is. Dit voorkomt race conditions waarbij Laravel probeert te starten terwijl de database nog niet beschikbaar is.
Geavanceerde health monitoring met custom endpoints
Voor productie-omgevingen bouw ik vaak uitgebreidere health checks die ook externe services controleren, zoals Redis-cache of API-endpoints die de applicatie nodig heeft. Deze checks plaats ik in een aparte controller om ze beter te organiseren:
// app/Http/Controllers/HealthController.php
class HealthController extends Controller
{
public function check()
{
$checks = [
'status' => 'healthy',
'timestamp' => now(),
'checks' => []
];
// Database check
try {
DB::connection()->getPdo();
$checks['checks']['database'] = 'ok';
} catch (Exception $e) {
$checks['checks']['database'] = 'failed';
$checks['status'] = 'unhealthy';
}
// Cache check
try {
Cache::put('health_check', 'test', 10);
$value = Cache::get('health_check');
$checks['checks']['cache'] = $value === 'test' ? 'ok' : 'failed';
} catch (Exception $e) {
$checks['checks']['cache'] = 'failed';
$checks['status'] = 'unhealthy';
}
// Queue check
try {
$queueSize = Queue::size();
$checks['checks']['queue'] = is_numeric($queueSize) ? 'ok' : 'failed';
} catch (Exception $e) {
$checks['checks']['queue'] = 'failed';
$checks['status'] = 'unhealthy';
}
$statusCode = $checks['status'] === 'healthy' ? 200 : 503;
return response()->json($checks, $statusCode);
}
}
Deze uitgebreide health check test meerdere kritieke componenten van de Laravel stack. Als een van deze services faalt, wordt de hele container als unhealthy gemarkeerd. In productie help dit om problemen snel te identificeren voordat gebruikers ze opmerken.
Belangrijk is dat health checks snel moeten zijn. Ik vermijd zware database-queries of time-outs die langer dan een paar seconden duren. Het doel is om de status van de container te controleren, niet om een volledige applicatie-test uit te voeren.
Monitoring en alerting met health check logs
Docker health checks genereren automatisch logs die ik kan monitoren met tools zoals Portainer of via de Docker CLI. Deze logs geven inzicht in de stabiliteit van containers over tijd:
# Health status van containers bekijken
docker ps --format "table {{.Names}}\t{{.Status}}"
# Health check logs van een specifieke container
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' container_name
# Alle unhealthy containers tonen
docker ps --filter health=unhealthy
Voor productie-omgevingen koppel ik deze health checks aan monitoring-systemen die automatisch alerts sturen wanneer containers unhealthy worden. Dit kan via Docker events die naar externe logging-systemen worden gestuurd, of door periodiek de health status te controleren met scripts.
Het mooie aan Docker health checks is dat ze naadloos integreren met orchestratie-tools. Kubernetes gebruikt deze informatie voor liveness probes, Docker Swarm kan services automatisch herstarten, en load balancers kunnen unhealthy containers uit rotatie halen. Door health checks goed te configureren, bouw ik automatische self-healing in mijn infrastructure die veel downtime voorkomt.
Health checks zijn een van die features die je pas echt waardeert wanneer ze je een keer hebben gered van een lange outage. Ze kosten weinig moeite om in te stellen, maar de stabiliteit die ze toevoegen aan productie-omgevingen is enorm waardevol.