Zero-Downtime Magento Deployment: A Complete Guide
The most expensive minute in e-commerce is the one where your store shows a 503 maintenance page during a deployment.
For most Magento stores, this means deploying at 2 AM to minimise impact. Your developers are tired, mistakes are more likely, and rollbacks are manual and stressful.
Why Magento deployments cause downtime
Magento's setup:upgrade command needs exclusive database access. During this window, the store can't serve requests. Traditional deployments handle this by enabling maintenance mode — showing a 503 page to all visitors.
The duration depends on your migrations. A simple module update might take 10 seconds. A complex schema change could take minutes.
The atomic symlink approach
Orbit uses a Capistrano-style release structure:
/var/www/
├── current -> releases/20260403_143022 (symlink)
├── releases/
│ ├── 20260403_143022/ (new release)
│ └── 20260403_120000/ (previous)
└── shared/
├── media/
└── var/
The new release is fully prepared in its own directory. Only the final symlink swap is instantaneous — switching current from the old release to the new one.
Cutover hold: the brief 503 you want
The symlink swap is instant, but PHP-FPM's opcache lags a few hundred milliseconds behind it. Requests landing in that window can hit a stale-pointer state — opcache still has the old vendor/, the symlink already points at the new release — and surface as the occasional Class not found 500.
Orbit closes the gap with a flag file. Before the swap, the agent touches shared/orbit/cutover-pending; after cache:flush succeeds, it removes it. A one-line nginx snippet returns 503 Retry-After: 3 while the flag exists. The window is typically 1–3 seconds — CDN retries cover it transparently, browsers honour Retry-After, and Orbit is never standing in for your nginx, never queuing TCP, never holding open connections.
The contract is fail-open: if the agent crashes mid-deploy, a watchdog clears the flag and traffic resumes to PHP-FPM. No nginx -s reload, no daemon to keep alive on port 80.
Honest maintenance windows for migrations
DB migrations are inherently not zero-downtime — you can't write to a table while ALTER TABLE is running. Orbit minimises the exposure rather than hiding it:
- Composer install,
di:compile, andstatic-content:deploy— the multi-minute steps — run in the new release directory while the site keeps serving from the old one. setup:db:statusruns first. If nothing's pending, maintenance mode never flips and the deploy is genuinely zero-downtime.- When migrations are needed, the maintenance window covers only
setup:upgrade— typically 10–60 seconds.
none, migrations, drift, always, or failed_rollback — rendered as a chip in the dashboard. "Zero-downtime ✓" is a per-deploy claim you can audit, not a product page promise.
Forward-compatible migrations
To keep schema-changing deploys in the "zero-downtime ✓" column, write migrations as two-deploy expand/contract pairs:
1. Expand: add the new column nullable; old and new code paths both read and write the old + new fields. 2. Contract: drop the old path; only the new column is used.
Each individual setup:upgrade finishes in seconds — you never take a multi-minute migration window. Same discipline Adobe Commerce Cloud uses internally; Orbit just makes it cheap to adopt without the $100k contract.
Built in Rust, for fewer wasted minutes
The agent is a single static Rust binary — no runtime to boot, no PHP bootstrap to call out to git or composer. The bigger time-saver, though, is Magento-aware smart-skip detection: CSS-only deploys skip setup:di:compile; PHP-only deploys skip setup:static-content:deploy. Typical deploys drop from ~3 minutes to ~75 seconds — and the shorter the deploy, the smaller every window above gets.
Auto-rollback
Health checks run automatically after every deploy. If the new release fails — HTTP errors, memory spikes, or custom checks — Orbit swaps the symlink back to the previous release, clears any leftover maintenance flag, and flushes cache on the restored release. No manual intervention.