← All articles

A Boring, Reliable Laravel Deployment Pipeline

How to deploy Laravel with release directories, atomic switches, safe migrations, queues, and a practical rollback path.

THE SHORT VERSION

Build each release separately, switch traffic atomically, and make rollback a routine operation rather than an incident improvisation.

The best deployment pipeline is usually boring. It performs the same small sequence every time, leaves evidence behind, and has a rollback path that does not depend on remembering shell history under pressure.

For a Laravel application on a Linux server, release directories and an atomic symlink switch are a strong baseline.

Use immutable release directories

Instead of updating the live directory in place, create one directory per release:

/var/www/app/
├── current -> releases/20260722-184500
├── releases/
│   ├── 20260718-102200/
│   └── 20260722-184500/
└── shared/
    ├── .env
    └── storage/

The web server points to current/public. A deployment prepares a new release completely, then updates the current symlink in one operation. Existing requests finish on the previous release while new requests use the new one.

Build before switching traffic

The new directory should receive the exact committed revision. Install PHP dependencies with production flags, build frontend assets in a controlled environment, link shared storage, and cache Laravel configuration.

composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache

Run a smoke check against the release before it becomes live. At minimum, confirm that PHP can boot the application, required files exist, and the compiled asset manifest is present.

Treat migrations as compatibility work

Zero-downtime application switching does not make every database migration safe. The old and new releases can overlap for a short period, and queue workers may still run old code.

Use an expand-and-contract approach:

  1. Add the new column or table without removing the old one.
  2. Deploy code that can work with both structures.
  3. Backfill data separately.
  4. Switch reads to the new structure.
  5. Remove the old structure in a later deployment.

Avoid a migration that renames or drops a column while the previous release still needs it. Large index changes and backfills should be planned as operations, not hidden inside the normal deployment.

Restart long-running workers

Queue workers keep application code in memory. After the symlink changes, ask Laravel workers to finish their current job and restart:

php artisan queue:restart

Your process manager should start the new worker automatically. Horizon deployments need the equivalent terminate signal. Confirm worker health as part of deployment validation; a green homepage does not prove that asynchronous work is running.

Make health checks meaningful

A health endpoint should verify that the application can serve a request. A deeper readiness check can include the database, cache, and queue connection, but avoid making every request depend on an expensive diagnostic.

After switching traffic, test one public route, one authenticated or API path when possible, and the queue system. Record the deployed revision and timestamp so support logs can be tied to a release.

Roll back code, roll forward data

Code rollback is the atomic symlink switch in reverse. Keep several recent releases on disk and expose one reviewed rollback action.

Database rollback is more dangerous. Destructive down() migrations can lose new data created after deployment. In production, prefer fixing schema problems with a new forward migration while returning application code to a compatible release.

Keep the pipeline small

A dependable pipeline generally needs these stages: fetch, install, build, link shared files, migrate safely, switch, restart workers, verify, and prune old releases.

Add notifications and approval gates where they match the risk, but do not bury the core sequence inside layers of automation no one can inspect. The goal is a process the team can explain, rehearse, and recover from.

End of field note.