DEV3 Technologies home

DEV3 Developer Forum

How can I deploy a web app with zero downtime using release folders and a symlink?

DevOps helpSolved0 views
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

1 reply

dev3 · Accepted answer

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

  • Reload PHP-FPM (or use 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.
  • With Nginx + PHP-FPM, pass $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.
  • Migrations must be backward compatible (expand then contract), because old and new code run at the same time for a moment.
  • Keep several old releases for instant rollback: repoint the symlink and reload.
  • Persist uploads and logs in shared/.

For Docker-based deploys, the equivalent is rolling updates behind a load balancer or docker compose up -d with healthchecks.

0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.