DEV Community

Magevanta
Magevanta

Posted on • Originally published at magevanta.com

Magento 2 Deployment: Zero-Downtime Production Deployments

Magento deployments done wrong mean minutes of downtime, broken shopping carts, and unhappy customers. Done right, they're invisible — customers never notice. Here's how to get there.

Why Magento deployments are hard

A typical Magento deployment touches:

  • PHP files (application code)
  • Generated code (var/generation/, generated/)
  • Static files (pub/static/)
  • Database schema and data
  • Cache (must be flushed)

The challenge: each of these operations takes time, and during that time your store is in an inconsistent state. New code referencing old database schema. New static files not yet deployed. Old cached content serving.

The naive approach (and why it fails)

# Bad: everything happens in-place
git pull origin main
composer install
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
php bin/magento cache:flush
Enter fullscreen mode Exit fullscreen mode

During setup:di:compile (2–5 minutes), your store serves requests without compiled DI — intermittent 500 errors. During setup:static-content:deploy, JS/CSS files may be missing. Classic downtime.

Approach 1: Maintenance mode (simple, has downtime)

php bin/magento maintenance:enable --ip=YOUR_IP

git pull origin main
composer install --no-dev --optimize-autoloader
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f en_US
php bin/magento cache:flush

php bin/magento maintenance:disable
Enter fullscreen mode Exit fullscreen mode

Maintenance mode shows a "temporarily unavailable" page. Downtime: 3–10 minutes depending on catalog size. Acceptable for small stores, not for high-traffic production.

Approach 2: Atomic symlink deployment (zero downtime)

The industry-standard approach: deploy to a new directory, then atomically switch the webserver symlink.

Directory structure:

/var/www/
├── releases/
│   ├── 20260419_143000/    ← current deployment
│   └── 20260419_120000/    ← previous deployment
├── shared/
│   ├── pub/media/          ← shared across deployments
│   ├── var/                ← shared var directory
│   └── app/etc/env.php     ← shared config
└── current -> releases/20260419_143000/  ← symlink
Enter fullscreen mode Exit fullscreen mode

Deployment script:

#!/bin/bash
set -e

RELEASE_DIR="/var/www/releases/$(date +%Y%m%d_%H%M%S)"
SHARED_DIR="/var/www/shared"
CURRENT_LINK="/var/www/current"

# 1. Create new release directory
mkdir -p "$RELEASE_DIR"
git clone --depth=1 git@github.com:yourorg/magento-store.git "$RELEASE_DIR"
cd "$RELEASE_DIR"

# 2. Install dependencies
composer install --no-dev --optimize-autoloader --no-interaction

# 3. Link shared directories
rm -rf pub/media var app/etc/env.php
ln -s "$SHARED_DIR/pub/media" pub/media
ln -s "$SHARED_DIR/var" var
ln -s "$SHARED_DIR/app/etc/env.php" app/etc/env.php

# 4. Build static assets and DI (while old release is still live)
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f en_US de_DE

# 5. Run database upgrades (safe to run before switch)
php bin/magento setup:upgrade --keep-generated

# 6. ATOMIC SWITCH — this is instant
ln -sfn "$RELEASE_DIR" "$CURRENT_LINK"

# 7. Flush caches (new code is now live)
php bin/magento cache:flush

# 8. Clean up old releases (keep last 3)
ls -t /var/www/releases | tail -n +4 | xargs -I{} rm -rf "/var/www/releases/{}"

echo "Deployment complete: $RELEASE_DIR"
Enter fullscreen mode Exit fullscreen mode

The key is step 6: ln -sfn is an atomic operation on Linux. The webserver symlink switches from old to new in a single filesystem operation — no window where the symlink is broken.

Approach 3: Blue-green deployment

For maximum safety: run two identical environments (blue and green), load balancer switches between them.

Load Balancer
├── Blue environment (magento-blue.internal)  ← currently live
└── Green environment (magento-green.internal) ← deploy here
Enter fullscreen mode Exit fullscreen mode

Process:

  1. Deploy to green while blue serves traffic
  2. Run smoke tests on green
  3. Switch load balancer to green (instant, no downtime)
  4. Blue becomes the new staging environment

Works best with containerized deployments (Docker/Kubernetes) or if your hosting provider supports it (AWS, GCP, Azure).

Database migrations without downtime

setup:upgrade runs database migrations. Running migrations while the store is live risks:

  • Old code reading new schema columns that don't exist yet
  • New code failing on old schema

Backward-compatible migration pattern:

Phase 1 (current deployment): add new column as nullable with default

ALTER TABLE sales_order ADD COLUMN new_field VARCHAR(255) DEFAULT NULL;
Enter fullscreen mode Exit fullscreen mode

Phase 2 (next deployment): make the column required, backfill data

UPDATE sales_order SET new_field = 'default_value' WHERE new_field IS NULL;
ALTER TABLE sales_order MODIFY COLUMN new_field VARCHAR(255) NOT NULL DEFAULT '';
Enter fullscreen mode Exit fullscreen mode

Phase 3 (later): remove old compatibility code

This "expand and contract" pattern lets you run migrations without the risk of old code breaking on new schema.

CI/CD pipeline example (GitHub Actions)

name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www
            ./scripts/deploy.sh

      - name: Smoke test
        run: |
          sleep 10
          curl -f https://your-store.com/ || exit 1
          curl -f https://your-store.com/catalog/product/view/id/1 || exit 1
Enter fullscreen mode Exit fullscreen mode

Post-deployment checklist

# Verify indexers aren't invalid
bin/magento indexer:status

# Verify caches are warmed
curl -s -o /dev/null -w "%{http_code} %{time_total}s" https://your-store.com/

# Check error logs
tail -50 var/log/system.log | grep -i error
tail -50 var/log/exception.log

# Verify static files deployed
curl -I https://your-store.com/static/frontend/Magento/luma/en_US/requirejs/require.js
Enter fullscreen mode Exit fullscreen mode

Zero-downtime deployments require upfront setup but pay off immediately — no more stress about deployment windows, no more customer complaints during releases.


Originally published on magevanta.com

Top comments (0)