Zero Downtime Deployment
debt(d7/e7/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints note automated=no and list tools like deployer, envoyer, kubernetes that assist with deployment orchestration but do not automatically detect violations of zero-downtime requirements. Issues such as abrupt PHP-FPM restarts, missing health checks, or destructive DB migrations before backward-compatible code are only caught by careful review of deployment scripts or by runtime testing — they do not surface until active users hit downtime.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix outlines four interdependent requirements: backwards-compatible DB migrations, graceful process shutdown, atomic symlink switching, and health checks before traffic routing. These span deployment pipeline configuration, database migration strategy, process management, and infrastructure setup — touching multiple components and often requiring coordinated changes across ops, application code, and schema management, making this well above a single-file fix.
Closest to 'persistent productivity tax' (b5). The choice applies to both web and cli contexts and tags indicate deployment and reliability concerns. Once a team adopts zero-downtime deployment practices, every future schema migration, API change, and deployment script must be written with backward compatibility and graceful shutdown in mind — this imposes an ongoing productivity tax across many work streams without necessarily defining the entire system's architecture.
Closest to 'serious trap' (t7). The misconception field explicitly states the canonical wrong belief: developers assume that placing a load balancer in front of multiple servers is sufficient for zero downtime. In practice, table-locking DB migrations, incompatible API changes between old and new code versions running simultaneously, and server-affine session state all cause downtime despite the load balancer. This contradicts the intuition that horizontal scaling alone solves the problem, making it a serious trap that contradicts how similar infrastructure concepts behave elsewhere.
Also Known As
TL;DR
Explanation
Zero downtime deployment for PHP applications uses several techniques. Atomic symlink swap (Deployer, Envoyer, Capistrano): deploy to a new release/ directory, run migrations and composer install, then atomically switch a current/ symlink — PHP-FPM continues serving the old release until the symlink swaps. Blue/green: two identical environments; the load balancer is flipped between them after the new version passes health checks. Rolling: gradually replace old instances. Critical requirements: database migrations must be backward compatible with the old code (never rename or drop a column in the same deploy as the code that uses it — separate into two deploys); session storage must be external (Redis); file uploads must use shared storage (S3). Use a deployment tool like Deployer to orchestrate the release sequence.
Common Misconception
Why It Matters
Common Mistakes
- Restarting PHP-FPM abruptly — use reload (graceful) not restart to finish in-flight requests.
- Running destructive database migrations before deploying backward-compatible code.
- Not using atomic symlink swaps — file-by-file rsync creates a window where old and new files mix.
- Health check that passes immediately after deploy before the app has fully initialised.
Code Examples
# Non-zero-downtime deploy:
systemctl stop php-fpm # Drops all in-flight requests
rsync -avz ./app/ /var/www/ # Files updated one by one — mixed state
systemctl start php-fpm # Cold start — first requests slow
# Zero-downtime with symlinks:
rsync -avz ./app/ /var/www/releases/v2/ # Prep in background
ln -sfn /var/www/releases/v2 /var/www/current # Atomic swap
systemctl reload php-fpm # Graceful — finishes in-flight
# Zero-downtime deploy sequence for PHP apps
# 1. Run DB migrations that are backwards-compatible
# (add columns/tables — don't remove until next deploy)
$ php artisan migrate --force
# 2. Warm OPcache on new code
$ php artisan optimize
# 3. Rolling restart PHP-FPM workers (graceful — finishes current requests)
$ sudo kill -USR2 $(cat /var/run/php-fpm.pid)
# USR2 = graceful reload — new workers start, old ones finish then exit
# 4. Verify health endpoint returns 200
$ curl --fail https://yourapp.com/health
# Kubernetes approach:
# Set rollingUpdate: maxUnavailable=0, maxSurge=1
# New pods start, health check passes, old pods terminate
# Blue-green: both versions running — switch load balancer
# Canary: route 5% traffic to new version first