← 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.

Bakry Abdalsalam builds websites, applications, integrations, and WordPress products. Bakry Dev Hub documents the technical decisions behind this work.

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.

For worker-specific verification, continue with Laravel queue and Supervisor troubleshooting. The Laravel scheduler and cron guide covers the other process that deployments commonly leave behind.

Common mistakes

Avoid running git pull in the live directory, dropping a database column while the previous release can still use it, leaving workers on old code, storing uploads inside a release, and deleting the rollback release before post-deployment checks pass.

Frequently asked questions

Can every migration be deployed without downtime?

No. Some schema operations lock large tables or break older code. Plan incompatible changes across expand, migrate, switch, and contract releases, based on the database engine and data volume.

When is a deployment complete?

After meaningful HTTP, database, asset, queue, and scheduler checks pass and error rates remain stable. A successful symlink switch is only one step.

Official references

Have a question about this guide or an idea for a technical collaboration? Contact Bakry through the Dev Hub.

End of field note.