Topic 66 of 75·M17
Laravel Deployment
Senior-levelthe production path (Nginx → PHP-FPM → Laravel → DB → Redis) · env vars · PHP extensions · Composer · migrations · storage permissions · queues · workers · scheduler · cache · OPcache
The one-sentence answer
Deploying Laravel is knowing the production request path and every moving part on it — Nginx → PHP-FPM → the app → MySQL/Postgres → Redis — plus migrations, workers, the scheduler, storage permissions, and caches.
The mental model
The checklist's diagram:
Nginx (web server)
↓ (static files served; PHP proxied)
PHP-FPM (PHP workers)
↓
Laravel (the app)
↓ ↓
MySQL/PostgreSQL Redis (cache, queues, sessions, rate limits)
Every box is a deploy concern: the web server serves static files and proxies PHP; PHP-FPM runs the app; the database holds the truth; Redis holds the fast shared state (Lesson 34). A production Laravel deploy touches all of them.
How it works — the deploy checklist
1. The server stack
Nginx → serves public/ (index.php, assets), proxies PHP to FPM
PHP-FPM → runs the app; PHP extensions must match the app (pdo_mysql/pdo_pgsql,
mbstring, openssl, curl, zip, gd/imagick, redis, opcache)
Laravel → the code, deployed and configured
MySQL/Postgres → the source of truth
Redis → cache, queues, sessions, rate limits
2. Environment — .env and secrets
# production .env lives in the deploy environment, NEVER in the repo (Lesson 37):
APP_ENV=production
APP_DEBUG=false # no error details leaked to clients
APP_KEY=<generated> # the encryption crown jewel (Lesson 38)
DB_* / REDIS_* / QUEUE_CONNECTION=redis / CACHE_STORE=redis / SESSION_DRIVER=redis
3. Composer & the app
composer install --no-dev --optimize-autoloader
# --no-dev: no dev packages in prod · --optimize-autoloader: classmap for speed
php artisan migrate --force # schema changes (no confirmation prompt)
php artisan optimize # config + route + view cache
4. Storage permissions
chown -R www-data:www-data storage bootstrap/cache
# Laravel writes logs, sessions, compiled views, and cached config to these
# (Lesson 39's logs + the compiled view cache)
5. Queues, workers & the scheduler
# queue workers — supervised (systemd, supervisor, Horizon — Lesson 27):
# restart them after a deploy so they run the new code
php artisan queue:restart
# the scheduler — ONE cron line (Lesson 32):
* * * * * cd /app && php artisan schedule:run >> /dev/null 2>&1
6. OPcache
# enabled in PHP — stop recompiling PHP files per request (Lesson 62's rung 10)
# validate_timestamps=0 with a cache-clear step on deploy, or validate on mtime
7. The deploy sequence (the ordering that matters)
1. pull the code (release dir + symlink for zero-downtime)
2. composer install --no-dev --optimize-autoloader
3. php artisan migrate --force (schema before new code depends on it)
4. php artisan optimize (config + route + view cache)
5. restart queue workers (they run the old code until now)
6. clear/reload OPcache (PHP files changed)
7. storage permissions (new files, writable dirs)
Migrations before workers is the critical ordering: the new code expects the new schema, and workers pick up the new code only after the restart step.
Interview questions
Q1. Walk me through a Laravel deployment.
Pull the code,
composer install --no-dev --optimize-autoloader, runphp artisan migrate --force, cache config/route/views (optimize), restart queue workers, reload OPcache, and check storage permissions. Env is set in the deploy environment withAPP_DEBUG=false; the.envnever comes from the repo. Workers restart after the schema so they run the new code against the new schema.
Q2. Why do workers need restarting on deploy?
Queue workers are long-lived processes holding the old code in memory — a deploy changes the files, but the running worker still executes the previous version.
php artisan queue:restart(or Horizon'shorizon:terminate) signals them to finish and restart on the new code (Lessons 26–27). Skipping it = the app runs new code while the queue runs old code.
Q3. What's the production request path?
Nginx receives the request — serves static files directly, proxies PHP to PHP-FPM. PHP-FPM runs Laravel's
public/index.phplifecycle (Lesson 106). The app reads/writes MySQL/PostgreSQL (the source of truth) and uses Redis for cache, queues, sessions, and rate limits (Lesson 34).
Q4. What environment settings matter in production?
APP_ENV=production,APP_DEBUG=false(no stack traces to clients — Lesson 40), a realAPP_KEY, and the production drivers — Redis for cache/queue/session (Lessons 33–34), and proper DB credentials. Secrets live in the deploy environment, never the repo (Lesson 37).
Q5. How do migrations fit into a deploy?
php artisan migrate --forceruns pending schema changes without the confirmation prompt — before the new code depends on the new schema, and before workers restart. The ordering matters: new schema first, then new code (including workers), then caches.
Senior follow-up: How do you roll back a bad deploy?
Two options: instant rollback — point the release symlink back to the previous release (the code is gone, the DB may have moved) — or
git revertplus a re-deploy for code-only issues. The DB is the hard part: if the migration was destructive, rolling back code doesn't roll back data — which is why destructive schema changes get a backup and a plan (Lesson 14). The senior answer: code rolls back fast; data needs a plan made before the deploy.
Common mistakes
❌ APP_DEBUG=true in production — stack traces and env details leaked to clients.
❌ Not restarting workers — the queue runs stale code after a deploy.
❌ Running migrations after the new code is live — the app hits a schema it doesn't know.
❌ composer install with dev packages in production — extra attack surface and dead weight.
❌ Forgetting storage permissions — logs and caches fail silently at first write.
Quick revision notes
- The path: Nginx → PHP-FPM → Laravel → MySQL/Postgres + Redis
- Deploy sequence: code →
composer install --no-dev --optimize-autoloader→migrate --force→optimize→ restart workers → reload OPcache → storage perms - Env:
APP_DEBUG=false, realAPP_KEY, Redis drivers, secrets in the environment (never the repo) - Workers restart after schema — they hold old code until then
- Scheduler: one cron line →
schedule:run(Lesson 32) - Rollback: code is instant (symlink); data needs a pre-made plan
Check your understanding
Answer each one out loud. Then rate yourself honestly.
0/5Recite the production request path and what each box does.
What's the critical ordering between migrations, workers, and code?
Why does --no-dev matter in production?
What breaks when storage permissions are wrong?
Why can't a code rollback undo a bad migration?