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

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Infrastructure as Code: Provisioning Secure Magento 2 Clusters on DigitalOcean Using Terraform

Infrastructure as Code: Provisioning Secure Magento 2 Clusters on DigitalOcean Using Terraform

Terraform Project Structure and Provider Configuration

We’ll begin by establishing a robust Terraform project structure. This modular approach enhances maintainability and scalability. Our primary configuration file, main.tf, will define the DigitalOcean provider and essential variables. A separate variables.tf file will house all input variables, promoting clarity and reusability. Finally, outputs.tf will expose key information about our provisioned infrastructure.

First, let’s configure the DigitalOcean provider. This block tells Terraform we’ll be interacting with DigitalOcean resources and specifies the authentication method. We’ll use an environment variable for the API token, which is a best practice for security.

main.tf

# main.tf

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

provider "digitalocean" {
  token = var.do_token
}

variable "do_token" {
  description = "DigitalOcean API Token"
  type        = string
  sensitive   = true
}

variable "region" {
  description = "The DigitalOcean region to deploy resources in."
  type        = string
  default     = "nyc3"
}

variable "ssh_key_name" {
  description = "Name of the SSH key to use for Droplet access."
  type        = string
}

variable "ssh_public_key_path" {
  description = "Path to the SSH public key file."
  type        = string
  default     = "~/.ssh/id_rsa.pub"
}

output "droplet_ids" {
  description = "List of IDs of the provisioned Droplets."
  value       = digitalocean_droplet.magento_web.*.id
}

output "droplet_ipv4_addresses" {
  description = "List of public IPv4 addresses of the provisioned Droplets."
  value       = digitalocean_droplet.magento_web.*.ipv4_address
}

output "load_balancer_ip" {
  description = "The public IP address of the Load Balancer."
  value       = digitalocean_loadbalancer.magento_lb.ip
}

Next, we define the input variables in variables.tf. This file centralizes all configurable parameters for our infrastructure.

variables.tf

# variables.tf

# (Content is already included in main.tf for brevity in this example,
# but in a larger project, it's good practice to separate them.)

And the outputs in outputs.tf, which will display useful information after deployment.

outputs.tf

# outputs.tf

# (Content is already included in main.tf for brevity in this example,
# but in a larger project, it's good practice to separate them.)

To initialize Terraform and set up the DigitalOcean provider, run:

terraform init

You’ll need to set your DigitalOcean API token as an environment variable. Replace YOUR_DIGITALOCEAN_API_TOKEN with your actual token.

export DO_TOKEN="YOUR_DIGITALOCEAN_API_TOKEN"

Ensure your SSH public key is registered with DigitalOcean and its name matches the ssh_key_name variable you’ll provide during the apply phase. You can add your SSH key via the DigitalOcean control panel or using the `doctl` CLI.

Provisioning Database and Cache Servers

A secure Magento 2 deployment requires a robust database and a fast caching layer. We’ll provision a managed PostgreSQL database and a Redis cache instance. Using DigitalOcean’s managed services offloads operational overhead and provides high availability.

database.tf

# database.tf

resource "digitalocean_database_cluster" "magento_db" {
  name      = "magento-db-cluster"
  engine    = "pg"
  version   = "13"
  region    = var.region
  size      = "db-s-1vcpu-1gb" # Adjust size based on expected load
  node_count = 1 # For production, consider 3 for high availability

  tags = ["magento", "database"]

  # Optional: Configure backup retention period
  # backup_restore_type = "backup"
  # backup_period = "daily"
  # backup_hour = "03:00"
}

resource "digitalocean_database_db" "magento_db" {
  cluster_id = digitalocean_database_cluster.magento_db.id
  name       = "magento_prod"
}

resource "digitalocean_database_user" "magento_user" {
  cluster_id = digitalocean_database_cluster.magento_db.id
  name       = "magento_app_user"
  password   = random_password.db_password.result # Use a generated password
}

resource "random_password" "db_password" {
  length           = 16
  special          = true
  override_special = "_%@"
}

output "db_host" {
  description = "Hostname of the managed PostgreSQL database."
  value       = digitalocean_database_cluster.magento_db.host
  sensitive   = true
}

output "db_port" {
  description = "Port of the managed PostgreSQL database."
  value       = digitalocean_database_cluster.magento_db.port
}

output "db_name" {
  description = "Name of the managed PostgreSQL database."
  value       = digitalocean_database_db.magento_db.name
}

output "db_user" {
  description = "Username for the managed PostgreSQL database."
  value       = digitalocean_database_user.magento_user.name
}

output "db_password" {
  description = "Password for the managed PostgreSQL database."
  value       = random_password.db_password.result
  sensitive   = true
}

cache.tf

# cache.tf

resource "digitalocean_redis" "magento_cache" {
  name       = "magento-cache-redis"
  engine     = "redis"
  version    = 6 # Or the latest stable version
  region     = var.region
  size_slug  = "basic-2" # Adjust size based on expected load
  redis_ எரிபொருள்_minutes = 0 # Disable automatic eviction for cache

  tags = ["magento", "cache"]
}

output "redis_host" {
  description = "Hostname of the managed Redis cache."
  value       = digitalocean_redis.magento_cache.host
  sensitive   = true
}

output "redis_port" {
  description = "Port of the managed Redis cache."
  value       = digitalocean_redis.magento_cache.port
}

These configurations create a PostgreSQL cluster and a Redis cluster. Note the use of random_password to generate a secure password for the database user. The outputs will provide connection details for your Magento application.

Configuring Web and Application Servers

We’ll deploy multiple Droplets for our Magento web application. A common pattern is to have at least two web servers behind a load balancer for redundancy and scalability. We’ll use a custom user data script to automate the initial setup, including installing necessary packages and configuring the web server.

servers.tf

# servers.tf

resource "digitalocean_ssh_key" "magento_ssh" {
  name       = var.ssh_key_name
  public_key = file(var.ssh_public_key_path)
}

resource "digitalocean_droplet" "magento_web" {
  count              = 2 # Number of web servers
  name               = "magento-web-${count.index}"
  region             = var.region
  size               = "s-2vcpu-4gb" # Adjust size based on expected load
  image              = "ubuntu-20-04-x64" # Or your preferred OS
  ssh_keys           = [digitalocean_ssh_key.magento_ssh.id]
  monitoring         = true
  user_data          = templatefile("${path.module}/scripts/setup_webserver.sh", {
    db_host     = digitalocean_database_cluster.magento_db.host
    db_port     = digitalocean_database_cluster.magento_db.port
    db_name     = digitalocean_database_db.magento_db.name
    db_user     = digitalocean_database_user.magento_user.name
    db_password = random_password.db_password.result
    redis_host  = digitalocean_redis.magento_cache.host
    redis_port  = digitalocean_redis.magento_cache.port
    ssh_public_key = file(var.ssh_public_key_path)
  })

  tags = ["magento", "webserver"]

  lifecycle {
    create_before_destroy = true
  }
}

The user_data script is crucial for bootstrapping the servers. It will install Nginx, PHP-FPM, and other dependencies, configure them to connect to the database and Redis, and set up a basic Nginx virtual host. We’re using templatefile to inject dynamic values from our Terraform state.

scripts/setup_webserver.sh

#!/bin/bash
set -e

# Update and install dependencies
apt-get update -y
apt-get upgrade -y
apt-get install -y nginx php-fpm php-mysql php-pgsql php-redis php-mbstring php-xml php-curl php-zip unzip git composer

# Configure PHP-FPM
sed -i 's/memory_limit = .*/memory_limit = 512M/' /etc/php/8.0/fpm/php.ini
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 64M/' /etc/php/8.0/fpm/php.ini
sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php/8.0/fpm/php.ini
sed -i 's/;daemonize = .*/daemonize = no/' /etc/php/8.0/fpm/php-fpm.conf # Ensure FPM runs in foreground

# Configure Nginx
cat > /etc/nginx/sites-available/magento <<EOF
server {
    listen 80;
    server_name _; # Catch all for now, will be handled by LB

    root /var/www/magento;
    index index.php index.html index.htm;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; # Adjust PHP version if needed
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
    }

    # Magento specific configurations (e.g., static content)
    location /static/ {
        expires 30d;
        add_header Cache-Control "public";
    }

    location /media/ {
        expires 30d;
        add_header Cache-Control "public";
    }
}
EOF

ln -sf /etc/nginx/sites-available/magento /etc/nginx/sites-enabled/magento
rm -f /etc/nginx/sites-enabled/default

# Create Magento directory and set permissions
mkdir -p /var/www/magento
chown www-data:www-data /var/www/magento

# Install Magento (placeholder - this should ideally be done via CI/CD or a separate provisioning step)
# For demonstration, we'll just create a dummy index.php
echo "<?php phpinfo(); ?>" > /var/www/magento/index.php
chown www-data:www-data /var/www/magento/index.php

# Restart services
systemctl restart nginx
systemctl restart php8.0-fpm # Adjust PHP version if needed

# Add SSH public key for easy access
mkdir -p /root/.ssh
echo "${ssh_public_key}" >> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys

echo "Web server setup complete."

The setup_webserver.sh script performs essential tasks: updating packages, installing Nginx, PHP-FPM, and necessary PHP extensions. It configures PHP-FPM for better performance and sets up a basic Nginx virtual host. Crucially, it injects the database and Redis connection details passed from Terraform. The script also adds the provided SSH public key to the root user’s authorized keys for immediate access.

Implementing a Load Balancer

To distribute traffic across our web servers and provide a single entry point, we’ll deploy a DigitalOcean Load Balancer. This also allows us to configure SSL termination and health checks.

loadbalancer.tf

# loadbalancer.tf

resource "digitalocean_loadbalancer" "magento_lb" {
  name     = "magento-lb"
  region   = var.region
  vpc_uuid = "YOUR_VPC_UUID" # Replace with your VPC UUID if not using default

  droplet_tag = "magento" # Tag applied to web server droplets

  forwarding_rule {
    entry_protocol   = "http"
    entry_port       = 80
    to_protocol      = "http"
    to_port          = 80
    certificate_id = "" # Optional: For SSL termination, provide a certificate ID
  }

  # Optional: Add HTTPS forwarding rule if SSL is terminated at the LB
  # forwarding_rule {
  #   entry_protocol   = "https"
  #   entry_port       = 443
  #   to_protocol      = "http"
  #   to_port          = 80
  #   certificate_id = "YOUR_DIGITALOCEAN_CERTIFICATE_ID" # Replace with your certificate ID
  # }

  healthcheck {
    port     = 80
    protocol = "http"
    path     = "/" # Basic health check, consider a dedicated health check endpoint
    check_interval_seconds = 10
    response_timeout = 5
    healthy_threshold = 3
    unhealthy_threshold = 3
  }

  tags = ["magento", "loadbalancer"]
}

In this configuration, the load balancer is set to forward HTTP traffic on port 80 to any Droplets tagged with “magento”. A basic HTTP health check is configured to ensure traffic is only sent to healthy web servers. For production, you would typically configure SSL termination here by providing a certificate_id and adding a forwarding rule for HTTPS.

Deployment and Verification

With all Terraform files in place, you can now provision your infrastructure. Ensure you have set the DO_TOKEN environment variable and have your SSH public key path correctly configured.

# Apply the Terraform configuration
terraform apply \
  -var="ssh_key_name=my-magento-ssh-key" \
  -var="ssh_public_key_path=~/.ssh/id_rsa.pub" \
  -var="region=nyc3"

Terraform will show you a plan of the resources it will create. Type yes to proceed. Once the apply is complete, Terraform will output the IP addresses of your web servers and the load balancer’s public IP.

Verification Steps:

  • Access the load balancer’s IP address in your browser. You should see the output of the dummy phpinfo() page.
  • SSH into one of the web Droplets using the private key corresponding to the public key you provided. For example: ssh root@<web_droplet_ip>.
  • Verify that Nginx and PHP-FPM are running: systemctl status nginx php8.0-fpm.
  • Check the Nginx access and error logs: /var/log/nginx/access.log and /var/log/nginx/error.log.
  • Inspect the PHP-FPM logs: /var/log/php8.0-fpm.log.
  • Confirm connectivity to the database and Redis from the web server: You can use `redis-cli -h <redis_host> -p <redis_port> ping` and `psql -h <db_host> -p <db_port> -U <db_user> -d <db_name> -c ‘\q’` (you’ll be prompted for the password).

This setup provides a foundational, secure, and scalable infrastructure for Magento 2 on DigitalOcean, managed entirely through Infrastructure as Code.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments
  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive
  • Leveraging PHP 8’s JIT Compiler and Vector APIs for Extreme Web Application Performance
  • Leveraging PHP 8 JIT and AWS Lambda for High-Performance, Serverless WordPress REST API Backends
  • Beyond the Basics: Leveraging PHP 8.3’s JIT Compiler and Fibers for High-Concurrency Laravel Applications

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (15)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (20)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (26)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments
  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive
  • Leveraging PHP 8's JIT Compiler and Vector APIs for Extreme Web Application Performance

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala