Introduction
Running a Laravel application in production is more than writing clean code and pushing it to a server. As a startup scales, the cost of a single outage or a slow release grows dramatically. This post walks through four concrete practices that go beyond the typical checklist: zero‑downtime deployments, feature flags, structured observability, and automated health checks. Each section includes a short code snippet or configuration file you can copy into an existing project.
1. Zero‑Downtime Deploys with Laravel Octane and Blue‑Green Releases
Traditional PHP deployments replace the entire codebase and restart the web server, causing a brief period where incoming requests hit a cold cache or a missing file. Two tools together eliminate that window:
- Laravel Octane – runs your app on Swoole or RoadRunner, keeping the application in memory between requests.
- Blue‑Green deployments – keep two identical environments (blue and green) behind a load balancer and switch traffic only after the new version passes health checks.
Step‑by‑step
-
Add Octane
composer require laravel/octane php artisan octane:install --server=swooleThis creates an
octane.phpconfig where you can tune workers, max‑request limits, and graceful shutdown time. -
Configure your CI/CD to build a Docker image that runs
php artisan octane:start --host=0.0.0.0 --port=8000. -
Set up blue‑green in your cloud provider (AWS Elastic Beanstalk, Azure App Service, or GCP Cloud Run). The pipeline should:
- Deploy the new image to the green environment.
- Run a health‑check script (see section 4).
- If healthy, switch the load balancer target group to green.
- Keep the blue version running for rollback.
Why it matters: Requests never hit a cold PHP-FPM process, and the switch is atomic at the load balancer level, giving you true zero‑downtime releases.
2. Feature Flags for Safer Releases
Feature flags let you ship code to production but keep new functionality hidden until you’re ready. Laravel has a lightweight package called spatie/laravel-feature-flags that stores flags in the database and caches them.
Installation
composer require spatie/laravel-feature-flags
php artisan vendor:publish --provider="Spatie\Feature\FeatureServiceProvider" --tag="migrations"
php artisan migrate
Defining a Flag
use Spatie\Feature\FeatureFacade as Feature;
Feature::define('new‑checkout', false); // default off
Using a Flag in Code
if (Feature::isEnabled('new-checkout')) {
return view('checkout.new');
}
return view('checkout.legacy');
Runtime Toggle
You can expose a tiny admin UI or use Laravel Nova to toggle flags, or call an Artisan command:
php artisan feature:enable new-checkout
php artisan feature:disable new-checkout
Best practice: Keep flags short‑lived. After a successful rollout, remove the flag and the dead code. Long‑standing flags become technical debt.
3. Structured Logging & Metrics
When an issue surfaces, raw log lines are hard to search. Structured logging (JSON) combined with a metrics collector gives you instant insight.
Logging with Monolog JSON Formatter
Add the formatter in config/logging.php:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'json'],
],
'json' => [
'driver' => 'single',
'path' => storage_path('logs/laravel-json.log'),
'level' => env('LOG_LEVEL', 'debug'),
'formatter' => Monolog\Formatter\JsonFormatter::class,
],
],
Now every Log::info('order.created', ['order_id' => $order->id]); produces a JSON line that can be ingested by Loki, Elastic, or CloudWatch.
Exporting Metrics with Prometheus
Install the client library:
composer require superbalist/laravel-prometheus-exporter
php artisan vendor:publish --provider="Superbalist\PrometheusExporter\PrometheusExporterServiceProvider"
Define a counter for request duration:
use Prometheus\CollectorRegistry;
use Prometheus\Histogram;
Route::middleware('web')->group(function () {
Route::get('/metrics', function (CollectorRegistry $registry) {
return response($registry->getMetricFamilySamples())->header('Content-Type', Prometheus\RenderTextFormat::MIME_TYPE);
});
});
Instrument the kernel middleware to observe request latency:
public function handle($request, Closure $next)
{
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
app('prometheus')->getOrRegisterHistogram('http_request_duration_seconds', 'Request duration', ['method', 'uri', 'status'])->observe($duration, [$request->method(), $request->path(), $response->getStatusCode()]);
return $response;
}
Prometheus scrapes /metrics, giving you per‑endpoint latency charts.
4. Automated Health Checks & Circuit Breakers
A load balancer can only know if a container is up, not if the app is healthy. Implement a /health endpoint that verifies database connectivity, cache, and a critical external service.
Health Endpoint
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
Route::get('/health', function () {
$checks = [];
// DB
try {
DB::connection()->getPdo();
$checks['database'] = 'ok';
} catch (\Exception $e) {
$checks['database'] = 'error';
}
// Cache
try {
Cache::store()->ping();
$checks['cache'] = 'ok';
} catch (\Exception $e) {
$checks['cache'] = 'error';
}
// External API (example payment gateway)
try {
$res = Http::timeout(1)->get('https://api.stripe.com/v1/health');
$checks['stripe'] = $res->successful() ? 'ok' : 'error';
} catch (\Exception $e) {
$checks['stripe'] = 'error';
}
$status = in_array('error', $checks) ? 503 : 200;
return response()->json($checks, $status);
});
Configure your cloud load balancer to hit /health every 10 seconds and mark the instance unhealthy on a 5xx response.
Circuit Breaker with Laravel Resilience
Use spatie/laravel-failure (or a simple wrapper) to prevent cascading failures when an external API is down.
use Spatie\Failure\Failure;
function chargeCustomer(array $payload): Failure|Response
{
return Failure::run('stripe-charge', fn() => Http::post('https://api.stripe.com/v1/charges', $payload));
}
When the circuit opens, Failure::run returns a predefined fallback, keeping your app responsive while the downstream service recovers.
5. Takeaway Checklist
- Deploy with Octane + blue‑green to eliminate downtime.
- Guard new code behind short‑lived feature flags; remove them after rollout.
- Log in JSON; ship logs to a centralized system.
- Expose Prometheus metrics for latency, error rates, and queue sizes.
- Implement a
/healthendpoint that checks DB, cache, and critical third‑party services. - Wrap external calls in a circuit‑breaker pattern.
Applying these practices shifts the focus from “will it work?” to “how quickly can we detect and recover when it doesn’t.” For a SaaS product, that speed translates directly into user trust and revenue continuity.
- Laravel
- Devops
- Performance
- Monitoring
- Feature Flags