Deployment
๐ Deployment
.env from Course 1's very first chapter โ the same closing structure the Django course used.
Nginx + PHP-FPM: A Different Shape From Gunicorn
Django's Course 2 covered Gunicorn as a long-running WSGI application server with worker processes. PHP's traditional model is genuinely different: PHP-FPM (FastCGI Process Manager) spins up a fresh PHP process (or reuses one from a pool) to handle each individual request, then discards its state โ PHP has traditionally never been a long-running application process the way a Gunicorn worker is:
Traditional Model: Nginx + PHP-FPM
Nginx serves static files and proxies dynamic requests to PHP-FPM, which boots the Laravel application fresh (or from a pool) for each request โ no in-memory state persists between requests by default.
Laravel Octane: The Newer Alternative
Keeps the application booted in memory across requests (via Swoole or RoadRunner), closer to how Gunicorn or Node actually works โ a real performance option, but not the traditional default.
โ๏ธ Artisan's Production Commands
Chapter 8 of Course 1 previewed config:cache and route:cache โ here's the full picture, plus one command that bundles them:
php artisan config:cache # combine config/*.php into one cached file php artisan route:cache # cache the compiled route table php artisan view:cache # pre-compile every Blade template to plain PHP # Or, all at once: php artisan optimize
Django's Checklist vs Laravel's Action Command
Django: check --deploy
A diagnostic tool โ it reports warnings about misconfiguration (DEBUG left on, missing security settings) but doesn't change anything itself.
Laravel: optimize
An action tool โ it directly performs the caching optimizations (config, routes, views) rather than just warning you they're missing.
Genuinely different philosophies solving adjacent problems: Django tells you what's wrong and lets you fix it; Laravel's optimize just does the optimization work directly, no separate "now go fix it" step.
Full Circle: .env Revisited
Back to Course 1, Chapter 1 โ the same two settings, the same risk, in production this time:
# .env โ production values APP_ENV=production APP_DEBUG=false # same risk as Django's DEBUG โ see this chapter's gotcha APP_KEY=base64:... # must be set โ Chapter 1's key:generate gotcha, still true in production DB_CONNECTION=mysql DB_HOST=your-production-db-host QUEUE_CONNECTION=redis # NOT sync โ Chapter 5's gotcha, still true here
๐ท Queue Workers Need a Process Supervisor
php artisan queue:work from Chapter 5 is a long-running process โ the same requirement Django's Gunicorn setup has, just for a different piece:
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
command=php /var/www/example-app/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=2
Supervisor (or systemd) keeps the worker running and restarts it automatically if it crashes โ directly parallel to how Django's Gunicorn process needs the same kind of process supervision to survive a server reboot or a crash.
Maintenance Mode: artisan down/up
A genuinely nice built-in feature worth introducing fresh here โ putting the whole site into a maintenance page is one command:
php artisan down --render="errors::503" # serves a maintenance page immediately, for every visitor # ... run migrations, deploy new code ... php artisan up # site is live again
A Full Deployment Sequence
Putting It All Together
php artisan down --render="errors::503" git pull origin main composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan optimize php artisan queue:restart # see this chapter's gotcha โ critical, easy to forget php artisan up
๐ป Coding Challenges
Challenge 1: Write Production .env Values
Given a .env with APP_DEBUG=true, QUEUE_CONNECTION=sync, and no APP_KEY, write the corrected production values and the command needed to generate a missing key.
Goal: Practice recognizing every environment-config risk covered across both courses in one place.
Challenge 2: Write a Deployment Script
Write the ordered shell commands for deploying an update: enabling maintenance mode, pulling code, installing dependencies, migrating, optimizing, restarting queue workers, then disabling maintenance mode.
Goal: Practice the full deployment sequence and reasoning about the correct order.
Challenge 3: Diagnose Stale Queue Worker Code
A deploy changes a Job's handle() method, but jobs processed after the deploy still run the OLD logic. Explain why, and what command fixes it.
Goal: Practice recognizing a deployment gotcha specific to long-running queue workers.
queue:restart After a DeployThis one is specific to queue systems and has no real Django parallel, since Django doesn't run persistent worker processes tied to a single deploy command the way Laravel's queue workers do. A queue:work process loads your application code (including every Job class) into memory once, when it starts, and keeps running indefinitely โ deploying new code to disk does not make a running worker pick it up. A worker that's been running since before the deploy keeps executing the old version of handle() for every job it processes, silently, with no error indicating the code is stale. php artisan queue:restart signals every worker to finish its current job and exit gracefully โ your process supervisor (Supervisor/systemd) then restarts them fresh, picking up the new code. This is exactly as easy to forget as APP_DEBUG=false, just far less obvious when it happens, since everything appears to work โ it's just running last week's logic.
๐ Course Complete
That closes Laravel Intermediate/Advanced โ and with it, the full Laravel project (16 chapters across both courses). Course 1 built the batteries-included fundamentals against FastAPI, Express, and Django; Course 2 layered on everything a real application needs beyond CRUD: authentication built on ownable, editable scaffolding rather than a closed framework box, a real API layer split cleanly between input validation and output shaping, a test suite whose safety net you have to opt into rather than inherit for free, decoupled events with a genuinely elegant path to background processing, a built-in queue system that solved the Django course's Celery gotcha automatically, object-level authorization by default, and finally a production deployment with its own genuinely PHP-specific traps. Across both courses, Laravel's own personality came through clearly: batteries included, but handed to you as code you're expected to read, own, and edit โ not a black box.