Zero-downtime Laravel deploys — the atomic-symlink pipeline that keeps queues honest
May 20, 2026 · 1 min read · by Sudhanshu K.
"Zero-downtime Laravel deploys" rarely are. The release directory is built fresh, the symlink is updated atomically — so far so good — but the queue workers are still pointing at the old code, and OPcache on the running PHP-FPM hasn't been reset, so requests after the symlink swap may execute a mix of old and new code paths for several seconds.
This is the deploy pipeline we ship that actually keeps deploys boring.
The release directory + atomic symlink
# /var/www/app/
# releases/
# 2026-05-20-123456/ ← new release directory
# 2026-05-19-090000/ ← previous, kept for rollback
# current → releases/2026-05-20-123456 ← atomic symlink
cd /var/www/app/releases
git clone --depth 1 git@github.com:org/app.git 2026-05-20-123456
cd 2026-05-20-123456
composer install --no-dev --optimize-autoloader
ln -s /var/www/app/shared/.env .env
ln -s /var/www/app/shared/storage storage
php artisan migrate --force --no-interaction
php artisan event:cache
php artisan route:cache
php artisan view:cache
ln -nfs /var/www/app/releases/2026-05-20-123456 /var/www/app/current
php artisan queue:restart
sudo systemctl reload php8.3-fpmln -nfs is atomic on POSIX — Nginx never sees a half-updated symlink. queue:restart tells workers to gracefully exit after their current job. systemctl reload php8.3-fpm resets OPcache without dropping connections.
The full write-up covers:
- Pre-deploy
composer install— never on the live host, always built elsewhere - The migration safety story: backward-compatible schema changes only
- Queue worker handoff — drain old workers, start new ones, no job loss
- OPcache reset timing relative to the symlink swap
- Rollback in 5 seconds: re-point the symlink to the previous release
- The
shared/directory pattern (.env,storage/, persistent uploads)
We ship this deploy pipeline on every managed Laravel customer.
Full article available
Read the full article