Skip to content

Docker Health Monitoring

Runtime optimizations and health monitoring for production/staging deployments using Docker ENTRYPOINT pattern and Spatie Health package.

Docker ENTRYPOINT pattern provides:

  • Runtime Laravel optimization at container startup
  • Health monitoring via Spatie Health package
  • Portable Docker images across environments
  • Consistent initialization across container restarts

Executes at container startup (runtime) instead of Docker build time:

  • Configures PHP limits from environment variables (PHP_MEMORY_LIMIT, PHP_POST_MAX_SIZE, PHP_UPLOAD_MAX_FILESIZE, PHP_MAX_EXECUTION_TIME, PHP_MAX_INPUT_TIME)
  • Tunes the PHP-FPM pool from environment variables (PHP_FPM_PM_MAX_CHILDREN, PHP_FPM_PM_START_SERVERS, PHP_FPM_PM_MIN_SPARE_SERVERS, PHP_FPM_PM_MAX_SPARE_SERVERS, PHP_FPM_REQUEST_SLOWLOG_TIMEOUT, PHP_FPM_REQUEST_TERMINATE_TIMEOUT, PHP_FPM_SLOWLOG, PHP_FPM_ACCESS_FORMAT)
  • php artisan filament:optimize - Optimizes FilamentPHP assets
  • php artisan optimize - Caches Laravel routes, views, config
  • Config cache uses actual runtime environment variables
  • Portable Docker images work across dev/staging/production
  • OptimizedAppCheck passes with proper caching state
  • Consistent initialization across container restarts

Shared entrypoint script: backend/docker/common/docker-entrypoint.sh

Used by:

  • php-fpm container
  • queue-worker container
  • scheduler container
Check Purpose Configuration
DatabaseCheck Verify database connectivity Default
CacheCheck Verify cache connectivity Default
UsedDiskSpaceCheck Monitor disk usage Warn: 70%, Fail: 90%
OptimizedAppCheck Verify Laravel optimization (prod/staging) Checks config/route cache
DebugModeCheck Ensure debug mode off (prod/staging) Fails if APP_DEBUG=true
EnvironmentCheck Verify correct environment (prod/staging) Checks APP_ENV value

Application errors and performance are monitored via:

  • Sentry (sentry/sentry-laravel) — Error tracking and performance tracing
  • Nightwatch — APM, ingested through a dedicated agent container (laravelphp/nightwatch-agent) defined in compose.yaml
  • Container health checks (HEALTHCHECK in each image)
php-fpm: # Web application (PHP-FPM)
backend: # Nginx front (serves php-fpm) — the "backend" ECR image
queue-worker: # Processes background jobs
scheduler: # Runs scheduled tasks (schedule:work)
nightwatch: # Nightwatch APM agent

Each of php-fpm, queue-worker, backend (nginx), and frontend is built as its own ECR image (see Deployment). The scheduler runs as a container but is not a separate ECR image — it reuses the queue-worker / php image with a different command.

Dedicated container running Laravel scheduler in production/staging:

Terminal window
php artisan schedule:work

Purpose: Executes the scheduled commands defined in backend/routes/console.php:

  • telescope:prune - Daily cleanup (keeps 14 days, preserves exceptions)
  • model:prune - Daily health history cleanup (1-day retention)
  • currency:sync-rates - Daily exchange rate sync from the configured provider
  • offers:auto-generate - Every 15 minutes, from flight cache entries
  • offers:draft-released - Daily, withdraws offers that entered the supplier release-days window
  • flights:mark-stale-searching-as-failed - Every 5 minutes, flips silently-dead flight cache rows to FAILED
  • ProcessScheduledBalancePaymentsJob (Job) - Hourly scheduled balance payments
  • db-backup.sh - Daily at 05:00 UTC, uploads a fresh DB dump to S3 (see Database Backup)

Deployment: Configured in infrastructure templates:

  • infra-volare/.../staging-1/.../api-staging-1.user-data.tmpl
  • infra-volare/.../production-1/.../api-production-1.user-data.tmpl
GET /health

Returns JSON with all check results:

{
"finishedAt": "2025-11-14 17:30:00",
"checkResults": [
{
"name": "Database",
"status": "ok"
},
{
"name": "Cache",
"status": "ok"
}
]
}

Response Codes:

  • 200 - All checks passed
  • 503 - One or more checks failed
GET /health-dashboard

Authenticated UI showing cached health check results (requires login).

Terminal window
php artisan health:check
Terminal window
HEALTH_NOTIFICATION_EMAIL=ops@example.com
HEALTH_DB_CONNECTION=pgsql

The queue worker has a separate Laravel memory threshold, configured in megabytes:

Terminal window
QUEUE_WORKER_MEMORY=384

The image defaults to 384. An empty override also uses the default. Any other value must be a positive integer, or the container exits during startup with a configuration error. Keep this threshold below the container memory limit so the worker can shut down cleanly before Docker terminates it.

File: config/health.php

  • Result storage: Database (1-day history)
  • Notifications: Email (throttled to 1/hour)

Configure a load balancer (e.g., AWS ALB) to poll the /health endpoint:

Health Check Settings:

  • Path: /health
  • Interval: 30 seconds
  • Timeout: 5 seconds
  • Healthy threshold: 2
  • Unhealthy threshold: 2
  1. Ensure ENTRYPOINT executes before service starts
  2. Check container logs for optimization errors
  3. Verify runtime environment variables set correctly
Terminal window
# Check if optimization ran
docker logs php-fpm | grep "artisan optimize"
  1. Check disk usage:
Terminal window
df -h
  1. Clean old logs:
Terminal window
php artisan telescope:prune
  1. Prune health history: Daily scheduled task handles this
  1. Check database connectivity:
Terminal window
php artisan tinker
>>> DB::connection()->getPdo();
  1. Verify environment variables:
Terminal window
php artisan config:show database
  1. Check Redis connectivity:
Terminal window
php artisan tinker
>>> Cache::get('test');
  1. Verify Redis configuration:
Terminal window
php artisan config:show cache
app/Providers/AppServiceProvider.php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Checks\DatabaseCheck;
Health::checks([
DatabaseCheck::new(),
CacheCheck::new(),
UsedDiskSpaceCheck::new()
->warnWhenUsedSpaceIsAbovePercentage(70)
->failWhenUsedSpaceIsAbovePercentage(90),
// Add custom checks here
]);
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
class QueueSizeCheck extends Check
{
public function run(): Result
{
$queueSize = Queue::size('default');
if ($queueSize > 1000) {
return Result::make()
->failed("Queue size is {$queueSize}");
}
if ($queueSize > 500) {
return Result::make()
->warning("Queue size is {$queueSize}");
}
return Result::make()
->ok("Queue size is {$queueSize}");
}
}