• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Zero-Downtime Blue-Green Deployment Pipelines for Magento 2 Applications on DigitalOcean

Zero-Downtime Blue-Green Deployment Pipelines for Magento 2 Applications on DigitalOcean

Understanding the Blue-Green Deployment Model for Magento 2

The blue-green deployment strategy is a cornerstone of achieving zero-downtime releases. It involves maintaining two identical production environments, “Blue” and “Green.” At any given time, one environment (e.g., Blue) is live and serving production traffic, while the other (Green) is idle. To deploy a new version, we deploy it to the idle environment (Green). Once tested and validated, traffic is switched from Blue to Green, making Green the new live environment. The old Blue environment is then kept as a rollback target or updated for the next deployment.

For Magento 2, this model presents unique challenges due to its complex architecture, including the database, cache layers (Varnish, Redis), session management, and static content generation. A successful implementation requires careful orchestration of these components across both environments.

Infrastructure Setup on DigitalOcean

We’ll leverage DigitalOcean Droplets and Load Balancers to manage our blue-green environments. Each environment will consist of:

  • A dedicated Load Balancer (e.g., DigitalOcean Load Balancer) to route traffic.
  • Multiple web server Droplets (e.g., Nginx with PHP-FPM) for the application layer.
  • A shared or replicated database instance (e.g., DigitalOcean Managed Databases for MySQL).
  • A shared or replicated cache layer (e.g., Redis).

Crucially, the database and cache layers must be accessible by both Blue and Green environments simultaneously during the transition. This often implies using a single, highly available database instance and a shared cache cluster. For stateful applications like Magento, this is a critical design decision.

Database Strategy: Replication vs. Shared Instance

The database is the most sensitive component. Two primary strategies exist:

  • Shared Database Instance: Both Blue and Green environments point to the same, highly available MySQL instance. This simplifies deployments as no data synchronization is needed between environments. However, it means any database schema changes must be backward-compatible, and a failure in the shared database impacts both environments.
  • Database Replication: Each environment has its own database instance, with the “live” environment’s database acting as the master and the “idle” environment’s database as a replica. During a deployment, the new code is deployed to the idle environment, and then the replica is promoted to master. This requires careful handling of data synchronization and potential write conflicts if the idle environment receives any writes before promotion. For Magento, a shared instance is generally preferred for simplicity and to avoid complex data migration scripts during cutover.

For this guide, we’ll assume a shared, highly available MySQL instance managed by DigitalOcean Managed Databases. This instance will be accessible by both the Blue and Green web server clusters.

Environment Provisioning with Terraform

Infrastructure as Code (IaC) is essential for managing identical environments. Terraform is an excellent choice for provisioning DigitalOcean resources.

Here’s a simplified Terraform configuration snippet for setting up two Nginx web server clusters (Blue and Green) and a Load Balancer.

Terraform Configuration (`main.tf`)

# DigitalOcean Provider
provider "digitalocean" {
  token = var.do_token
}

# Variables (e.g., DO_TOKEN, DB_HOST, DB_USER, DB_PASSWORD)
variable "do_token" {
  description = "DigitalOcean API Token"
  type        = string
  sensitive   = true
}

variable "db_host" {
  description = "Database Hostname"
  type        = string
}

variable "db_user" {
  description = "Database User"
  type        = string
}

variable "db_password" {
  description = "Database Password"
  type        = string
  sensitive   = true
}

# Load Balancer
resource "digitalocean_loadbalancer" "magento_lb" {
  name     = "magento-production-lb"
  region   = "nyc3"
  droplet_ids = [
    digitalocean_droplet.web_blue_1.id,
    digitalocean_droplet.web_blue_2.id,
    digitalocean_droplet.web_green_1.id,
    digitalocean_droplet.web_green_2.id
  ]

  forwarding_rule {
    entry_protocol   = "http"
    entry_port       = 80
    target_protocol  = "http"
    target_port      = 80
    target_backend   = digitalocean_loadbalancer_backend.web_backend.id
  }

  forwarding_rule {
    entry_protocol   = "https"
    entry_port       = 443
    target_protocol  = "http" # Assuming Nginx handles SSL termination
    target_port      = 443
    target_backend   = digitalocean_loadbalancer_backend.web_backend.id
  }

  healthcheck {
    port     = 80
    path     = "/healthz" # A simple health check endpoint
    protocol = "http"
  }
}

resource "digitalocean_loadbalancer_backend" "web_backend" {
  name = "web-backend"
  # Droplet IDs will be populated by the loadbalancer resource
}

# Blue Environment Web Servers
resource "digitalocean_droplet" "web_blue_1" {
  name    = "magento-web-blue-1"
  region  = "nyc3"
  size    = "s-2vcpu-4gb"
  image   = "ubuntu-22-04-x64"
  ssh_keys = [data.digitalocean_ssh_key.my_ssh_key.id]

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file("~/.ssh/id_rsa") # Adjust path as needed
    host        = self.ipv4_address
  }

  provisioner "remote-exec" {
    inline = [
      "apt-get update -y",
      "apt-get install -y nginx php-fpm php-mysql redis-server", # Basic packages
      # Further configuration for Nginx, PHP-FPM, Magento setup will be done via Ansible/Chef/scripts
    ]
  }
}

resource "digitalocean_droplet" "web_blue_2" {
  name    = "magento-web-blue-2"
  region  = "nyc3"
  size    = "s-2vcpu-4gb"
  image   = "ubuntu-22-04-x64"
  ssh_keys = [data.digitalocean_ssh_key.my_ssh_key.id]

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file("~/.ssh/id_rsa")
    host        = self.ipv4_address
  }

  provisioner "remote-exec" {
    inline = [
      "apt-get update -y",
      "apt-get install -y nginx php-fpm php-mysql redis-server",
    ]
  }
}

# Green Environment Web Servers (Identical to Blue)
resource "digitalocean_droplet" "web_green_1" {
  name    = "magento-web-green-1"
  region  = "nyc3"
  size    = "s-2vcpu-4gb"
  image   = "ubuntu-22-04-x64"
  ssh_keys = [data.digitalocean_ssh_key.my_ssh_key.id]

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file("~/.ssh/id_rsa")
    host        = self.ipv4_address
  }

  provisioner "remote-exec" {
    inline = [
      "apt-get update -y",
      "apt-get install -y nginx php-fpm php-mysql redis-server",
    ]
  }
}

resource "digitalocean_droplet" "web_green_2" {
  name    = "magento-web-green-2"
  region  = "nyc3"
  size    = "s-2vcpu-4gb"
  image   = "ubuntu-22-04-x64"
  ssh_keys = [data.digitalocean_ssh_key.my_ssh_key.id]

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file("~/.ssh/id_rsa")
    host        = self.ipv4_address
  }

  provisioner "remote-exec" {
    inline = [
      "apt-get update -y",
      "apt-get install -y nginx php-fpm php-mysql redis-server",
    ]
  }
}

# SSH Key Data Source
data "digitalocean_ssh_key" "my_ssh_key" {
  name = "MySSHKeyName" # Replace with your SSH key name in DigitalOcean
}

This Terraform code defines the basic Droplets and the Load Balancer. The actual Magento installation, Nginx configuration, and PHP-FPM setup would typically be managed by a configuration management tool like Ansible, Chef, or custom shell scripts executed via Terraform’s `remote-exec` provisioner or a CI/CD pipeline.

Application Deployment Workflow

The core of zero-downtime deployment lies in how we update the application code and switch traffic. We’ll use a CI/CD pipeline (e.g., GitLab CI, GitHub Actions, Jenkins) to automate this.

Step-by-Step Deployment Process

  1. Trigger Deployment: A new code commit to the main branch triggers the CI/CD pipeline.
  2. Provision/Update Idle Environment: The pipeline targets the “Green” environment (which is currently idle). It deploys the new Magento code, runs composer install, generates static content, and clears caches.
  3. Database Migrations (if any): If there are database schema changes, they must be applied to the shared database instance. This is where backward-compatible schema changes are crucial. If non-backward-compatible changes are required, a more complex multi-step process involving data migration and potential downtime for writes might be necessary.
  4. Pre-flight Checks: Run automated tests (unit, integration, smoke tests) against the Green environment. This includes checking critical user flows, API endpoints, and admin functionalities.
  5. Traffic Switching: This is the critical step. We update the DigitalOcean Load Balancer configuration to point traffic from the Blue environment to the Green environment.
  6. Post-flight Checks: Monitor the Green environment closely for errors, performance degradation, or unexpected behavior.
  7. Rollback (if necessary): If issues are detected, immediately switch traffic back to the Blue environment by updating the Load Balancer.
  8. Update Idle Environment: Once the Green environment is stable and serving traffic, the old Blue environment becomes the idle environment. It can be updated with the new code for the next deployment or kept as a standby for a quick rollback.

Automating Code Deployment and Configuration

Ansible is a powerful tool for automating the deployment of Magento code and configuration across the web server Droplets. We’ll define roles for Nginx, PHP-FPM, and Magento itself.

Ansible Playbook Snippet (`deploy.yml`)

---
- name: Deploy Magento 2 Application
  hosts: webservers # Dynamic inventory group
  become: yes
  vars:
    magento_root_dir: "/var/www/html/magento"
    magento_version: "2.4.6" # Example version
    app_env: "production"
    db_host: "{{ hostvars[groups['all'][0]]['db_host'] }}" # Assuming db_host is passed or defined
    db_user: "{{ hostvars[groups['all'][0]]['db_user'] }}"
    db_password: "{{ hostvars[groups['all'][0]]['db_password'] }}"
    redis_host: "10.132.0.5" # Example Redis IP
    redis_port: 6379

  tasks:
    - name: Ensure Magento root directory exists
      file:
        path: "{{ magento_root_dir }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    - name: Fetch latest Magento code (example: Git pull)
      git:
        repo: "[email protected]:your-repo/magento2.git"
        dest: "{{ magento_root_dir }}"
        version: "{{ magento_version }}" # Or 'main' for latest
        force: yes
      notify:
        - Composer Install
        - Magento Setup Upgrade
        - Magento Di Compile
        - Magento Static Content Deploy
        - Clear Magento Cache

    - name: Configure Nginx for Magento
      template:
        src: templates/nginx.conf.j2
        dest: "/etc/nginx/sites-available/magento.conf"
      notify:
        - Reload Nginx

    - name: Enable Nginx site
      file:
        src: "/etc/nginx/sites-available/magento.conf"
        dest: "/etc/nginx/sites-enabled/magento.conf"
        state: link
      notify:
        - Reload Nginx

    - name: Configure PHP-FPM pool (if needed)
      template:
        src: templates/php-fpm.conf.j2
        dest: "/etc/php/8.1/fpm/pool.d/magento.conf" # Adjust PHP version
      notify:
        - Restart PHP-FPM

  handlers:
    - name: Composer Install
      command: "composer install --no-dev --optimize-autoloader"
      args:
        chdir: "{{ magento_root_dir }}"
      listen: "Composer Install"

    - name: Magento Setup Upgrade
      command: "bin/magento setup:upgrade --keep-generated"
      args:
        chdir: "{{ magento_root_dir }}"
      listen: "Magento Setup Upgrade"

    - name: Magento Di Compile
      command: "bin/magento setup:di:compile"
      args:
        chdir: "{{ magento_root_dir }}"
      listen: "Magento Di Compile"

    - name: Magento Static Content Deploy
      command: "bin/magento setup:static-content:deploy -f en_US en_GB" # Add your locales
      args:
        chdir: "{{ magento_root_dir }}"
      listen: "Magento Static Content Deploy"

    - name: Clear Magento Cache
      command: "bin/magento cache:clean && bin/magento cache:flush"
      args:
        chdir: "{{ magento_root_dir }}"
      listen: "Clear Magento Cache"

    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded
      listen: "Reload Nginx"

    - name: Restart PHP-FPM
      service:
        name: php8.1-fpm # Adjust PHP version
        state: restarted
      listen: "Restart PHP-FPM"

This playbook assumes you have a dynamic inventory set up that can target the web servers for the current active environment (Blue or Green). The `hosts: webservers` would be dynamically populated by your CI/CD system.

Traffic Management with DigitalOcean Load Balancer API

The most critical part of the zero-downtime switch is updating the Load Balancer. This can be automated using the DigitalOcean API. We’ll need to dynamically update the `droplet_ids` associated with the Load Balancer.

API Interaction Script (Python Example)

import requests
import json
import os

# --- Configuration ---
DO_API_TOKEN = os.environ.get("DIGITALOCEAN_API_TOKEN")
LOAD_BALANCER_ID = "your-load-balancer-id" # Get this from your Terraform output or DO dashboard
BLUE_DROPLET_IDS = [12345678, 12345679] # IDs for Blue environment Droplets
GREEN_DROPLET_IDS = [98765432, 98765433] # IDs for Green environment Droplets
CURRENT_ACTIVE_ENV = "blue" # Or "green"

DO_API_URL = f"https://api.digitalocean.com/v2/loadbalancers/{LOAD_BALANCER_ID}"
HEADERS = {
    "Authorization": f"Bearer {DO_API_TOKEN}",
    "Content-Type": "application/json"
}

def get_loadbalancer_config():
    response = requests.get(DO_API_URL, headers=HEADERS)
    response.raise_for_status()
    return response.json()

def update_loadbalancer_droplets(new_droplet_ids):
    lb_config = get_loadbalancer_config()
    
    # Find the backend that needs updating. Assuming one backend for web servers.
    # You might need to adapt this if you have multiple backends.
    backend_to_update = None
    for backend in lb_config['load_balancer']['backends']:
        if backend['name'] == 'web-backend': # Match the backend name from Terraform
            backend_to_update = backend
            break
            
    if not backend_to_update:
        print("Error: Backend 'web-backend' not found.")
        return

    # Construct the payload for the update
    # We need to update the entire load balancer configuration, not just the backend.
    # This is a simplified example; a real-world scenario might involve more complex
    # handling of forwarding rules and health checks.
    
    payload = {
        "droplet_ids": new_droplet_ids,
        "algorithm": lb_config['load_balancer']['algorithm'],
        "sticky_sessions": lb_config['load_balancer']['sticky_sessions'],
        "healthcheck": lb_config['load_balancer']['healthcheck'],
        "forwarding_rules": lb_config['load_balancer']['forwarding_rules']
    }

    # Update the specific backend within the payload if necessary, but often
    # updating the main droplet_ids is sufficient if the backend is implicitly linked.
    # For simplicity, we'll just update the main droplet_ids.
    
    update_url = DO_API_URL
    response = requests.put(update_url, headers=HEADERS, data=json.dumps(payload))
    
    if response.status_code == 200:
        print(f"Successfully updated Load Balancer {LOAD_BALANCER_ID} with droplets: {new_droplet_ids}")
    else:
        print(f"Error updating Load Balancer: {response.status_code} - {response.text}")
        response.raise_for_status()

def switch_to_green():
    print("Switching traffic to Green environment...")
    update_loadbalancer_droplets(GREEN_DROPLET_IDS)

def switch_to_blue():
    print("Switching traffic to Blue environment...")
    update_loadbalancer_droplets(BLUE_DROPLET_IDS)

if __name__ == "__main__":
    # Example usage:
    # In a real CI/CD pipeline, you'd have logic to determine which env to switch to.
    # For demonstration, let's assume we want to switch to Green.
    
    # First, ensure Green is ready and tested.
    # Then, call switch_to_green()
    
    # If something goes wrong, call switch_to_blue() for rollback.
    
    # Example: Switch to Green
    switch_to_green()
    
    # Example: Rollback to Blue
    # switch_to_blue()

This Python script interacts with the DigitalOcean API to update the Droplets associated with the Load Balancer. In a CI/CD pipeline, this script would be executed after the Green environment has been successfully deployed and tested. The `CURRENT_ACTIVE_ENV` variable would be managed by the pipeline’s state.

Session Management and Cache Considerations

Magento is a stateful application, and managing user sessions and cache across deployments is critical for a seamless user experience.

Session Handling

If users are in the middle of a transaction when traffic is switched, their session must persist. Using a shared Redis instance for session storage is highly recommended. This ensures that sessions are not lost during the cutover.

// app/etc/env.php
 [
        'frontName' => 'admin_secret'
    ],
    'crypt' => [
        'key' => 'your_application_key'
    ],
    'db' => [
        'table_prefix' => '',
        'connection' => [
            'default' => [
                'host' => 'your_db_host',
                'dbname' => 'your_db_name',
                'username' => 'your_db_user',
                'password' => 'your_db_password',
                'model' => 'mysql4',
                'initStatements' => 'SET NAMES utf8',
                'options' => [
                    PDO::ATTR_PERSISTENT => true,
                    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
                ]
            ]
        ]
    ],
    'resource' => [
        'default_setup' => [
            'connection' => 'default'
        ]
    ],
    'session' => [
        'save' => 'redis',
        'redis' => [
            'host' => 'your_redis_host',
            'port' => 6379,
            'password' => '',
            'timeout' => 2.5,
            'persistent_identifier' => '',
            'database' => 0,
            'compression_threshold' => 2048,
            'compression_library' => 'gzip',
            'log_level' => 3,
            'max_concurrency' => 6,
            'break_after_frontend' => true,
            'break_after_adminhtml' => true,
            'first_lifetime' => 600,
            'bot_first_lifetime' => 60,
            'bot_lifetime' => 60,
            'frontend_options' => [
                'compress_data' => true,
                'compression_level' => 6,
                'write_timeout' => 1,
                'read_timeout' => 5,
                'automatic_cleaning_factor' => 0,
                'compress_tags' => 1,
                'compress_sections' => 1
            ],
            'admin_options' => [
                'compress_data' => false,
                'compress_sections' => false
            ]
        ]
    ],
    'cache' => [
        'frontend' => [
            'default' => [
                'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
                'options' => [
                    'server' => 'your_redis_host',
                    'port' => 6379,
                    'database' => 1, # Use a different DB for cache
                    'password' => '',
                    'compress_data' => '1',
                    'compression_library' => 'gzip'
                ]
            ],
            'page_cache' => [
                'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
                'options' => [
                    'server' => 'your_redis_host',
                    'port' => 6379,
                    'database' => 2, # Use another DB for page cache
                    'password' => '',
                    'compress_data' => '1',
                    'compression_library' => 'gzip'
                ]
            ]
        ]
    ]
];

Cache Management

Similarly, a shared Redis instance for Magento’s cache (including Varnish if used) is crucial. During deployment, you’ll clear the cache on the newly deployed environment. If Varnish is used, its configuration might need to point to the shared Redis for ESI (Edge Side Includes) fragments.

Health Checks and Monitoring

Robust health checks are vital for both the Load Balancer and the application itself. The Load Balancer’s health check endpoint should verify that the web server is running and responding. The application’s health check endpoint should perform more in-depth checks, such as database connectivity and essential service availability.

Application Health Check Endpoint (`app/code/Vendor/Module/Controller/Adminhtml/Healthcheck.php`)

<?php
namespace Vendor\Module\Controller\Adminhtml;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\App\ResourceConnection;

class Healthcheck extends Action
{
    protected $resultJsonFactory;
    protected $resourceConnection;

    public function __construct(
        Context $context,
        JsonFactory $resultJsonFactory,
        ResourceConnection $resourceConnection
    ) {
        parent::__construct($context);
        $this->resultJsonFactory = $resultJsonFactory;
        $this->resourceConnection = $resourceConnection;
    }

    public function execute()
    {
        $result = $this->resultJsonFactory->create();
        $status = 'OK';
        $details = [];

        // Check database connection
        try {
            $connection = $this->resourceConnection->getConnection();
            $connection->fetchOne('SELECT 1'); // Simple query to test connection
            $details['database'] = 'Connected';
        } catch (\Exception $e) {
            $status = 'ERROR';
            $details['database'] = 'Connection failed: ' . $e->getMessage();
        }

        // Add checks for Redis, Elasticsearch, etc. as needed

        $result->setData([
            'status' => $status,
            'details' => $details,
            'timestamp' => time()
        ]);

        if ($status === 'ERROR') {
            $this->getResponse()->setHttpResponseCode(503); // Service Unavailable
        }

        return $result;
    }
}

This endpoint should be configured in your Nginx virtual host to be accessible at `/healthz` (or a similar path) and should be excluded from Varnish caching. The Load Balancer health check should target this endpoint.

Rollback Strategy

A robust rollback strategy is as important as the deployment itself. If any issues are detected post-deployment:

  • Immediate Traffic Reversal: Use the DigitalOcean API script (or your CI/CD equivalent) to switch traffic back to the Blue environment.
  • Analyze and Fix: Investigate the cause of the failure in the Green environment.
  • Revert Code: If the issue is code-related, revert the commit and redeploy the previous stable version to the Green environment.
  • Database Rollback (Complex): If database schema changes caused the issue and are not backward-compatible, this is the most challenging part. It might involve restoring from a database snapshot (which implies downtime) or executing complex data migration scripts in reverse. This highlights the importance of backward-compatible schema changes.

The old Blue environment, now idle, serves as the immediate rollback target. Once the issue is resolved and the Green environment is stable, it can be updated for the next deployment cycle.

Conclusion

Implementing zero-downtime blue-green deployments for Magento 2 on DigitalOcean requires meticulous planning and automation. Key considerations include shared database and cache infrastructure, robust health checks, automated deployment pipelines, and a well-defined traffic switching mechanism using the Load Balancer API. By carefully orchestrating these components, you can achieve highly available Magento 2 deployments with minimal to zero downtime.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing indexing lock conflicts and high CPU during bulk stock updates on DigitalOcean Servers
  • How to Debug and Fix memory leaks and socket exhaustion in daemon processes in Modern C++ Applications
  • Infrastructure as Code: Provisioning Secure PHP Clusters on DigitalOcean Using Terraform
  • Fixing Slow Largest Contentful Paint (LCP) caused by unoptimized database queries in Legacy Laravel Codebases Without Breaking API Contracts
  • An Auditor’s Checklist for Securing Laravel Backends on Google Cloud

Copyright © 2026 · Vinay Vengala