Skip to content
Mehedi Hasan Sagor

// blog

Laravel Production Playbook: Proven Practices to Ship Faster and Safer

Mehedi Hasan Sagor Oct 7, 2026 5 min read
Laravel Production Playbook: Proven Practices to Ship Faster and Safer

Why a Laravel Playbook Matters

When a startup moves from MVP to a paying customer base, the margin for error shrinks dramatically. A single latency spike or a mis‑configured environment can turn early adopters into churned users. Laravel gives you a rich ecosystem, but the framework’s defaults are tuned for developer convenience, not production robustness. This playbook distills the most critical, repeatable practices you can enforce today to keep your Laravel codebase reliable, fast, and secure.

1. Environment & Configuration Hygiene

Keep Secrets Out of the Repo

Never commit .env files or API keys. Use a secrets manager (AWS Parameter Store, HashiCorp Vault, or Laravel Envoyer’s encrypted variables) and reference them via config() helpers.

// config/services.php
return [
    'stripe' => [
        'key' => env('STRIPE_KEY'),
        'secret' => env('STRIPE_SECRET'),
    ],
];

During deployment, inject the values with your CI tool’s secret store. This prevents accidental leaks and makes rotating credentials painless.

Enforce a Single Source of Truth

Store all configuration in config/*.php files and access them through the config() helper. Avoid calling env() outside of configuration files; otherwise the value will be cached and you’ll lose the ability to change it without a full cache clear.

// BAD – calling env() in a Service Provider
public function register()
{
    $this->app->singleton(PaymentGateway::class, function () {
        return new StripeGateway(env('STRIPE_KEY'));
    });
}

// GOOD – read from config()
public function register()
{
    $this->app->singleton(PaymentGateway::class, function () {
        return new StripeGateway(config('services.stripe.key'));
    });
}

Cache Configuration & Routes

Run these artisan commands as part of your build step:

php artisan config:cache   # compiles all config files into a single PHP file
php artisan route:cache    # caches route definitions (avoid if you use closures)
php artisan view:cache     # pre‑compiles Blade views

The resulting files are pure PHP, eliminating the overhead of parsing .env and config files on every request.

2. Automated Testing & Continuous Integration

Test Pyramid for Laravel

| Layer | Purpose | Typical Tool | |-------|---------|--------------| | Unit | Pure PHP logic, no framework boot | PHPUnit | | Feature | HTTP requests, middleware, DB interactions | Laravel's makesHttpRequests trait | | End‑to‑End | Full stack, browser UI | Cypress / Laravel Dusk |

Aim for 70 % unit, 20 % feature, 10 % E2E. The exact percentages vary, but the key is to keep the fast‑running unit suite dominant.

CI Pipeline Essentials

  1. Static Analysis – Run phpstan or psalm at level 8. It catches type errors before they hit production.
  2. Code Style – Enforce PSR‑12 with phpcs and auto‑fix with phpcbf.
  3. Tests – Execute php artisan test --parallel to leverage multiple cores.
  4. Security Scan – Use symfony/security-checker (or composer audit) to detect vulnerable dependencies.

A minimal GitHub Actions workflow:

name: Laravel CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8
        env:
          MYSQL_ROOT_PASSWORD: secret
          MYSQL_DATABASE: test
        ports: ['3306:3306']
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
    steps:
      - uses: actions/checkout@v3
      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: mbstring, intl, pdo_mysql
          coverage: none
      - name: Install Composer deps
        run: composer install --prefer-dist --no-interaction --no-progress
      - name: Static analysis
        run: vendor/bin/phpstan analyse --level=8
      - name: Run tests
        env:
          DB_CONNECTION: mysql
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
          DB_DATABASE: test
          DB_USERNAME: root
          DB_PASSWORD: secret
        run: php artisan test --parallel

3. Performance & Caching Strategies

Query Optimization

  • Select only needed columns – User::select('id', 'name')->get();
  • Chunk large datasets – User::chunk(500, fn($users) => …);
  • Eager load relationships – Post::with('author')->paginate(15);

Avoid the N+1 problem; Laravel’s debug bar or clockwork will highlight it during local testing.

Cache Layers

  1. Application Cache – Store expensive calculations or API responses in Redis or Memcached.
    $stats = Cache::remember('dashboard.stats', now()->addMinutes(10), fn() => $this->calculateStats());
    
  2. Response Cache – For public endpoints, use spatie/laravel-responsecache to serve a full HTML response from Redis.
  3. Route Cache – Already covered, but remember it only works with controller‑based routes, not closures.

Queue Workers

Offload anything that can be done asynchronously: emails, image processing, PDF generation. Use Laravel Horizon to monitor Redis‑backed queues and set appropriate retry_after values to avoid duplicate jobs.

php artisan queue:work --daemon --timeout=60 --tries=3

4. Security & Observability

Harden the HTTP Layer

  • Force HTTPS – Add \App\Providers\AppServiceProvider::class:
    public function boot()
    {
        if ($this->app->environment('production')) {
            URL::forceScheme('https');
        }
    }
    
  • Rate Limiting – Leverage Laravel’s ThrottleRequests middleware for login and API endpoints.
  • Content Security Policy – Use spatie/laravel-csp to define allowed script and style sources.

Input Validation

Never trust request data. Define Form Request classes and always validate before persisting.

class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'product_id' => 'required|exists:products,id',
            'quantity'   => 'required|integer|min:1',
            'address'    => 'required|string|max:255',
        ];
    }
}

Logging & Alerting

  • Structured Logs – Switch to Monolog’s JSON formatter so logs can be indexed by ELK or Loki.
  • Error Tracking – Integrate Sentry or Bugsnag; they capture stack traces and provide real‑time alerts.
  • Health Checks – Expose /health endpoint that verifies DB, cache, and queue connectivity. Use a tool like upptime to monitor uptime.

5. Takeaways: A Checklist for Your Next Deploy

| ✅ | Action | |---|--------| | 1 | Store all secrets in a vault; never commit .env. | | 2 | Cache config, routes, and views as part of the build step. | | 3 | Maintain a test pyramid; run static analysis and security scans in CI. | | 4 | Optimize queries, eager load relationships, and use chunk for large sets. | | 5 | Layer caching: Redis for data, response cache for HTML, Horizon for queues. | | 6 | Enforce HTTPS, CSP, and rate limiting in production. | | 7 | Use structured JSON logs and an error‑tracking service. | | 8 | Add a /health endpoint and monitor it with an external service. |

Implementing these practices doesn’t require a full rewrite; you can adopt them incrementally. Start with the low‑hanging fruit—environment hygiene and CI pipelines—then layer on caching, queue workers, and observability. The result is a Laravel application that scales with your user base, stays secure under scrutiny, and gives your engineering team confidence to ship fast.

  • Laravel
  • Security
  • Devops
  • Performance
  • Testing