CLOUD
development

Zero-Downtime Deployments: Blue-Green, Rolling, and Symlink Strategies in Practice

CLOUD|September 16, 2026|7 min read|210 views
Zero-Downtime Deployments: Blue-Green, Rolling, and Symlink Strategies in Practice

Zero-downtime deployment comes in three shapes: swapping a symlink on one server covers most apps, Blue-Green runs two parallel copies for critical upgrades, and Rolling updates several servers behind a balancer. They differ in cost and complexity, not in the outcome.

Zero-downtime deployment means updating your app without any visitor seeing an error or a maintenance page, and it comes in three strategies ordered from simplest to most complex. First: symlink releases, enough for most PHP and Laravel apps on one server. You build the new release in a separate folder, then flip a single symlink named current toward it in a near-instant operation. Second: Blue-Green, running two identical full copies (blue live, green new) and switching all traffic at once after verification, which needs double the resources or a second server. Third: Rolling, updating several servers one by one behind a load balancer so the service stays up on the rest during the update.

For most companies running a custom system on a single cloud server, symlink releases (the Deployer and Envoyer pattern) is the right choice: zero downtime, instant rollback, and no extra cost. You only need Blue-Green or Rolling when upgrading the PHP version itself, making an incompatible database change, or already running several servers. The real challenge in all three is not the switch, it is the shared state: sessions, cache, uploaded files, and database migrations. We break down the three methods with numbers and steps, then handle the shared state.

Comparing the three strategies

CriterionSymlinkBlue-GreenRolling
Servers needed11 with double resources, or 22 or more
DowntimeZeroZeroZero
Rollback speedInstant (flip symlink)Instant (switch traffic)Gradual
ComplexityLowMediumHigh
Extra costNoneDouble resources at deploy timeSeveral servers + balancer
Best forPHP/Laravel app on one serverCritical upgrades, platform version changeHigh distributed traffic

The idea is that your web root does not point directly at the code, it points at a symlink named current that points to a specific release inside a releases folder. On each deploy you build a full new release in a timestamped folder, prepare it completely (install packages, build assets, run migrations), then flip the symlink in a single atomic step. The typical layout:

  • releases/: each release in a folder named by date and time.
  • current: a symlink pointing to the live release.
  • shared/: files stable across releases (env, uploads, logs).

The flip itself is safe because ln -sfn on Linux is atomic, so there is never a moment where current is broken. An in-flight request finishes on the old release, and the next request starts on the new one. To roll back, you point the symlink at the previous release in under a second. Keep the last five releases for fast rollback and delete older ones to save space, since each release uses disk, and add 100 GB SSD for 500 SAR per year if needed.

The typical flow for a single deploy runs through these ordered steps:

  • Pull the new code into a fresh timestamped release folder inside releases.
  • Link the shared files (env and uploads) from the shared folder into the new release.
  • Install packages (composer install) and build assets (npm build) inside the new release while it is out of service.
  • Run forward-compatible database migrations.
  • Flip the current symlink to the new release with the atomic ln -sfn.
  • Reload PHP-FPM to clear opcache of the old release's files.
  • Verify the new release's health, and if it fails, point the symlink back immediately.

A critical step: reload PHP-FPM after the flip

PHP caches file paths in opcache, so after flipping the symlink FPM may serve old-release files from cache. The fix is to reload PHP-FPM after the flip, or enable opcache.revalidate_freq. This is a common mistake that makes a deploy look successful while users stay on old code.

Strategy two: Blue-Green

Here you run two full environments: blue serves traffic now, and green is a new copy you prepare completely and test on an internal port or subdomain. When ready, you switch all traffic from blue to green by changing the balancer or reverse-proxy config. The advantage is that you test the new environment with real load before switching, and rollback is an instant reverse switch. The cost is double resources during the deploy, so either a temporary second server or a server with double the RAM. This method is justified when upgrading the PHP version or the OS, or any change that cannot be tested safely on the same environment. To run two parallel environments temporarily, upgrading your Cloud Server plan for the deploy window and reverting is a practical option. An added benefit: the old environment stays ready as a safety net for minutes after the switch, so if a fault appears under real traffic, you return to it at once.

Strategy three: Rolling

You have several servers behind a load balancer, so you update them one by one: pull a server out of the balancer, update it, verify its health, put it back, then move to the next. The service never stops because the rest serve while each is updated. The advantage is no need for double resources all at once, and the drawback is that during the deploy you run two versions together for a while, so the code and database must be forward and backward compatible. This method suits high-traffic apps already distributed across more than one server, and it is the most complex of the three, requiring careful health checks at each step, with a readiness check that stops the balancer from returning a server whose update has not completed.

Shared state: where failures actually happen

The switch is the easy part. The real failures live in the state shared between versions:

  • Sessions: if you store sessions in files inside the release folder, every user is logged out on each deploy. Store them in Redis, the database, or a stable shared folder.
  • Cache: clear config and route cache after the flip, not before, and rebuild it on the new release, otherwise it serves old routes.
  • Uploaded files: keep them in a shared folder outside releases, otherwise they disappear when old releases are deleted.
  • Database migrations: make them forward and backward compatible. Add columns before deploying the code that uses them, and drop old ones in a later deploy after the new version settles, so neither version breaks the other during overlap.
  • Queues: restart queue workers after the deploy so they load the new code, since they do not pick up the change automatically.
  • Scheduled tasks (Cron): make sure they point at current, not at a deleted old release path.

Health-check before declaring success

Do not treat the deploy as successful just because the symlink flipped. Add an automatic health check after the flip: request a simple internal page (like /health) that returns the state of the database, cache, and queues, and if it returns anything but 200, roll back immediately. This check is the difference between a one-second rollback and learning about the fault from a customer half an hour later. Make it part of the deploy script, not a manual step that gets forgotten, and add a response-time check on the home page, since a deploy can succeed technically yet slow the app unacceptably because of one heavy new query, and the automatic check catches that before your customer complains.

Automating the deploy with a tool

Do not run these steps by hand every time, since human error is likely at the switch and the rollback. Use a tool that automates the whole flow: building the release, linking shared, running migrations, flipping the symlink, reloading FPM, and the health check, with an automatic rollback on any failure. This makes deployment one command any team member repeats with the same result, turning a deploy from an anxious event into an invisible daily routine. Log every deploy with its timestamp and release number so a rollback is a clear decision, not a tense search during an outage.

The practical decision

For most companies: start with symlink releases on a single server, which is zero downtime, zero cost, and instant rollback, and covers 95% of cases. Move to Blue-Green only when upgrading the platform version or making a fundamental environment change. Reach for Rolling only if you already run several servers behind a balancer. In every method, fix the shared state first: sessions in Redis, uploads in shared, compatible migrations, an FPM reload after the flip, and an automatic health check. Set these up once, and every later deploy becomes an event invisible to your customers.

Share