dev3 ·
Each deploy I run git pull and composer install in the live folder, and visitors see errors for a minute. How do I switch versions atomically?
#deployment#zero-downtime#linux
0
DEV3 Developer Forum
Every post is reviewed first. Usually reviewed within 24 hours.
Each deploy I run git pull and composer install in the live folder, and visitors see errors for a minute. How do I switch versions atomically?
#deployment#zero-downtime#linux
Build each release in its own directory and switch a current symlink in one atomic step. This is the approach used by Capistrano, Deployer and Envoyer.
Layout
/var/www/app/
releases/20261109120000/
releases/20261108090000/
shared/.env
shared/storage/
current -> releases/20261109120000
Nginx root points to /var/www/app/current/public.
Deploy script (simplified)
set -euo pipefail
APP=/var/www/app
REL=$APP/releases/$(date +%Y%m%d%H%M%S)
git clone --depth 1 --branch main git@github.com:you/app.git "$REL"
cd "$REL"
composer install --no-dev --optimize-autoloader --no-interaction
npm ci && npm run build
ln -s $APP/shared/.env "$REL/.env"
rm -rf "$REL/storage" && ln -s $APP/shared/storage "$REL/storage"
php artisan migrate --force
php artisan config:cache && php artisan route:cache
ln -sfn "$REL" $APP/current.tmp && mv -Tf $APP/current.tmp $APP/current
sudo systemctl reload php8.3-fpm
php artisan queue:restart
ls -1dt $APP/releases/* | tail -n +6 | xargs rm -rf
mv -T over a symlink is an atomic rename, unlike ln -sfn alone which briefly removes then recreates the link.
Important details
opcache.validate_timestamps/realpath_cache care): OPcache keys by real path, but a stale realpath cache may keep serving the old release. Reloading clears it.$realpath_root instead of $document_root in SCRIPT_FILENAME (fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;) so OPcache sees the new release path after the switch.shared/.For Docker-based deploys, the equivalent is rolling updates behind a load balancer or docker compose up -d with healthchecks.