March 07, 2026

Zero-Downtime Laravel Deployments: A Guide for Full-Stack Developers

By Paresh Prajapati • Lead Architect

Zero-Downtime Laravel Deployments: A Guide for Full-Stack Developers

The Anxiety of the 'Deploy' Button

We have all been there. It is 3 PM on a Tuesday, your codebase is ready, and you need to push an update to your live production server. If your deployment process involves pulling code directly into your live web directory, running composer install, and executing migrations while users are actively navigating your app, you are playing with fire.

During those 30 to 60 seconds of deployment, users might hit broken routes, encounter missing dependencies, or face database lockups. Professional server administration demands a better way. Enter Zero-Downtime Deployments.

How Zero-Downtime Deployments Work

The core concept behind zero-downtime deployment is that you never alter the directory your web server (Nginx or Apache) is actively serving. Instead, you use a symlink (symbolic link) strategy.

Here is the architectural breakdown of the server directory structure:

  • /var/www/my-app/releases/ - This directory holds multiple folders, one for every deployment you make (named by timestamp, e.g., 20260307101500).
  • /var/www/my-app/shared/ - This holds files that must persist across deployments, specifically your .env file and your storage/ directory (where user uploads live).
  • /var/www/my-app/current - This is not a real folder. It is a symlink pointing to the latest directory inside the releases/ folder.

Your Nginx configuration is set to serve from /var/www/my-app/current/public.

The Deployment Flow

When you trigger a deployment (via a CI/CD pipeline like GitHub Actions or a tool like Laravel Envoyer), the server executes the following sequence in the background:

  1. Clone the Code: A brand new folder is created in releases/ (e.g., release_B). The new code is cloned here.
  2. Install Dependencies: composer install and npm run build are executed entirely inside release_B. The live app (pointing to release_A) is completely unaffected.
  3. Link Shared Storage: The script creates symlinks from release_B/storage to the shared/storage folder, and links the shared .env file.
  4. Optimize: Laravel optimization commands (route:cache, config:cache, view:cache) are run on the new release.
  5. The Switch: This is the magic moment. In a fraction of a millisecond, the symlink at /var/www/my-app/current is updated to point from release_A to release_B.

Traffic is instantly routed to the fully prepared, cached, and compiled new version of your application. The user experiences absolutely zero downtime.

Handling Database Migrations safely

While the code deployment is now seamless, database migrations remain tricky. If a migration drops a column that the old code (still running for a few active requests) relies on, the app will crash.

The golden rule for zero-downtime database management is to make your migrations additive-only during a deployment. If you need to drop a column or drastically change a schema, it must be done in multiple separate deployments:

  1. Deploy 1: Add the new column. Update the code to write to both the old and new columns, but read from the old.
  2. Deploy 2: Update the code to read from the new column.
  3. Deploy 3: Drop the old column safely.

Conclusion

Setting up a symlink-based deployment pipeline is a rite of passage for full-stack developers. It removes the stress from shipping code, allows you to deploy multiple times a day with confidence, and most importantly, ensures your users never see a maintenance page.

Paresh Prajapati
Lead Architect, Smart Tech Devs