--- title: Zero-Downtime Laravel Deployments Without Drama category: DevOps published: 2026-07-10 reading_time: 9 min canonical: https://technologiesninja.com/blog-zero-downtime-laravel-deploy.html --- # Zero-Downtime Laravel Deployments Without Drama Zero-downtime is less about a fancy tool and more about never pointing traffic at a half-finished release. Deploys that require a maintenance window train a team to deploy rarely, and rare deploys are big, risky deploys. Zero-downtime deployment breaks that loop. The mechanics are not complicated; the discipline is in never letting live traffic see a release that is not fully assembled. ## Atomic releases via symlink swap Build each release into its own timestamped directory, fully — code, dependencies, compiled assets — while the current release keeps serving. When it is complete, flip a current symlink to the new directory. The web server always points at current, so the switch is atomic: one instant it is the old release, the next it is the new one, with nothing in between. ``` releases/ 2026-07-10-101500/ # new build, assembled in full 2026-07-09-183000/ # previous, kept for rollback current -> releases/2026-07-10-101500 # atomic flip shared/ # .env, storage, persist across releases ``` Rollback is then just flipping the symlink back to the previous directory — seconds, not a rebuild. Keep .env, storage and other stateful paths in a shared directory symlinked into each release so nothing important lives inside a release you might delete. ## Migrations are where downtime hides An atomic code swap does not save you if a migration locks a large table or if new code hits an old schema. The fix is expand-then-contract: make schema changes backward-compatible, deploy code that works with both shapes, then remove the old shape in a later step. - Add columns as nullable or with a default; never rename in place while old code runs - Backfill data in batched jobs, not in the migration that blocks the deploy - Split a rename into add-new, dual-write, migrate-reads, drop-old across deploys - Watch for implicit locks on large tables; on big data, add indexes concurrently > A migration that takes a table lock is a maintenance window in disguise, no matter how atomic your symlink swap is. ## Order of operations for caches and workers Laravel caches config, routes and views for speed, which means a deploy has to rebuild them at the right moment — against the new release, after the swap, not before. And queue workers hold old code in memory until restarted, so a deploy that does not restart them keeps running yesterday’s job code against today’s schema. - Rebuild config/route/view caches for the new release as part of the build step - Restart or gracefully signal queue workers so they pick up the new code - Let in-flight jobs finish rather than killing them mid-run ## Tools are the easy part Deployer, Envoy, or a CI pipeline all implement this pattern; the tool matters less than getting the sequence right. Whatever runs it, the steps are the same: build the release in isolation, run non-blocking migrations, warm caches, flip the symlink, restart workers, and keep the last few releases for instant rollback. ## The payoff When a deploy is atomic and rollback is one command, shipping stops being an event. You deploy small changes often, you deploy them in daylight, and when something is wrong you flip back in seconds instead of debugging a broken production. That change in cadence is worth more than any single performance tweak. --- ## Read next - Article — PHP-FPM Tuning for Laravel Under Load → /blog-php-fpm-tuning - Article — Laravel Horizon in Production: Queues That Fail Loudly → /blog-laravel-horizon-production - Service — DevOps & Performance → /service-devops-performance --- © 2026 TechnologiesNinja · Built and maintained in-house