• 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 » Infrastructure as Code: Provisioning Secure WooCommerce Clusters on DigitalOcean Using Terraform

Infrastructure as Code: Provisioning Secure WooCommerce Clusters on DigitalOcean Using Terraform

Terraform Project Structure and Provider Configuration

We’ll begin by establishing a robust Terraform project structure. This organization is crucial for managing complexity, especially as our infrastructure grows. Our root module will house the primary configuration files.

First, let’s define the DigitalOcean provider. This block tells Terraform that we intend to manage resources on DigitalOcean and specifies the authentication method. For security, we’ll use an API token, ideally sourced from an environment variable to avoid hardcoding credentials.

main.tf – Provider Setup

# 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 "project_name" {
  description = "A prefix for all resources created."
  type        = string
  default     = "secure-woo"
}

To use this, you’ll need to set the DO_TOKEN environment variable:

export DO_TOKEN="your_digitalocean_api_token_here"

Provisioning Core Infrastructure: VPC, Databases, and Load Balancers

A secure WooCommerce cluster necessitates a well-defined network. We’ll start by creating a Virtual Private Cloud (VPC) to isolate our resources. Within this VPC, we’ll provision a managed PostgreSQL database for WooCommerce’s data and a highly available Load Balancer to distribute traffic.

network.tf – VPC and Firewall

# network.tf

resource "digitalocean_vpc" "main" {
  name     = "${var.project_name}-vpc"
  region   = var.region
  ip_range = "10.10.0.0/16"
}

resource "digitalocean_firewall" "woocommerce_fw" {
  name = "${var.project_name}-firewall"
  # Associate firewall with the VPC
  vpc_ids = [digitalocean_vpc.main.id]

  # Allow SSH for management
  inbound_rule {
    protocol    = "tcp"
    ports       = ["22"]
    sources {
      addresses = ["0.0.0.0/0"] # Restrict this in production
    }
  }

  # Allow HTTP/HTTPS for WooCommerce
  inbound_rule {
    protocol    = "tcp"
    ports       = ["80", "443"]
    sources {
      addresses = ["0.0.0.0/0"] # Load balancer will handle external access
    }
  }

  # Allow internal communication within the VPC
  inbound_rule {
    protocol    = "tcp"
    ports       = ["0-65535"]
    sources {
      droplet_ids = [] # Will be populated by droplet resources later
      load_balancer_ids = [digitalocean_loadbalancer.main.id]
      vpc_ids = [digitalocean_vpc.main.id]
    }
  }
  inbound_rule {
    protocol    = "udp"
    ports       = ["0-65535"]
    sources {
      droplet_ids = [] # Will be populated by droplet resources later
      load_balancer_ids = [digitalocean_loadbalancer.main.id]
      vpc_ids = [digitalocean_vpc.main.id]
    }
  }
  inbound_rule {
    protocol    = "icmp"
    sources {
      droplet_ids = [] # Will be populated by droplet resources later
      load_balancer_ids = [digitalocean_loadbalancer.main.id]
      vpc_ids = [digitalocean_vpc.main.id]
    }
  }

  # Allow outbound traffic
  outbound_rule {
    protocol    = "tcp"
    ports       = ["0-65535"]
    destinations {
      addresses = ["0.0.0.0/0"]
    }
  }
  outbound_rule {
    protocol    = "udp"
    ports       = ["0-65535"]
    destinations {
      addresses = ["0.0.0.0/0"]
    }
  }
  outbound_rule {
    protocol    = "icmp"
    destinations {
      addresses = ["0.0.0.0/0"]
    }
  }
}

Note the `vpc_ids` association for the firewall. In a production environment, you would significantly restrict the `0.0.0.0/0` source for SSH to specific IP ranges or bastion hosts.

database.tf – Managed PostgreSQL

# database.tf

resource "digitalocean_database_cluster" "woocommerce_db" {
  name      = "${var.project_name}-db"
  engine    = "pg"
  version   = "14" # Specify your desired PostgreSQL version
  region    = var.region
  size      = "db-s-2vcpu-4gb" # Adjust size based on expected load
  node_count = 1 # For production, consider 2+ for high availability

  # Associate with the VPC for private networking
  vpc_uuid = digitalocean_vpc.main.id

  # Enable automatic backups
  backup_restore_type = "automated"

  # Configure database name and user
  database {
    name = "woocommerce_db"
    # Password will be automatically generated and accessible via output
  }

  # Configure maintenance window
  maintenance_window {
    day  = "sunday"
    hour = "03:00"
  }
}

output "woocommerce_db_host" {
  description = "The hostname of the WooCommerce database."
  value       = digitalocean_database_cluster.woocommerce_db.host
  sensitive   = true
}

output "woocommerce_db_port" {
  description = "The port of the WooCommerce database."
  value       = digitalocean_database_cluster.woocommerce_db.port
}

output "woocommerce_db_name" {
  description = "The name of the WooCommerce database."
  value       = digitalocean_database_cluster.woocommerce_db.database[0].name
}

output "woocommerce_db_user" {
  description = "The username for the WooCommerce database."
  value       = digitalocean_database_cluster.woocommerce_db.username
  sensitive   = true
}

output "woocommerce_db_password" {
  description = "The password for the WooCommerce database."
  value       = digitalocean_database_cluster.woocommerce_db.password
  sensitive   = true
}

We’re using sensitive = true for outputs that contain credentials. These will be masked in Terraform output and can be securely accessed via the Terraform state or by referencing them in other resources.

loadbalancer.tf – High Availability Load Balancer

# loadbalancer.tf

resource "digitalocean_loadbalancer" "main" {
  name     = "${var.project_name}-lb"
  region   = var.region
  vpc_uuid = digitalocean_vpc.main.id

  # Droplets will be added dynamically via a count or for_each
  # For now, we'll define the health check and forwarding rules

  healthcheck {
    port     = 80
    protocol = "http"
  }

  forwarding_rule {
    entry_protocol   = "http"
    entry_port       = 80
    target_protocol  = "http"
    target_port      = 80
    target_backend   = digitalocean_loadbalancer.main.droplet_ids # Placeholder, will be populated
  }

  forwarding_rule {
    entry_protocol   = "https"
    entry_port       = 443
    target_protocol  = "https"
    target_port      = 443
    target_backend   = digitalocean_loadbalancer.main.droplet_ids # Placeholder, will be populated
    # SSL certificate details would go here for HTTPS
    # ssl_certificate_id = digitalocean_certificate.my_cert.id
  }

  # Enable sticky sessions if needed for certain WooCommerce plugins
  # sticky_sessions {
  #   type = "cookies"
  #   cookie_name = "woocommerce_session"
  #   cookie_ttl = 3600
  # }
}

# Placeholder for SSL certificate resource if using HTTPS directly on LB
# resource "digitalocean_certificate" "my_cert" {
#   name = "${var.project_name}-ssl-cert"
#   private_key = file("path/to/your/private.key")
#   certificate = file("path/to/your/certificate.crt")
# }

output "loadbalancer_ip" {
  description = "The public IP address of the load balancer."
  value       = digitalocean_loadbalancer.main.ip
}

The droplet_ids in the forwarding rules are placeholders. These will be dynamically populated once we define our Droplet resources and associate them with the load balancer.

Deploying WooCommerce Application Servers

We’ll deploy multiple Droplets to host our WooCommerce application. These Droplets will be configured with Nginx as a reverse proxy and PHP-FPM to serve the WordPress/WooCommerce application. Using a count meta-argument allows us to easily scale the number of application servers.

app.tf – Application Droplets

# app.tf

resource "digitalocean_droplet" "app_server" {
  count    = 2 # Start with 2 app servers, adjust as needed
  name     = "${var.project_name}-app-${count.index}"
  region   = var.region
  size     = "s-2vcpu-4gb" # Adjust size based on expected load
  image    = "ubuntu-22-04-x64" # Or your preferred OS
  vpc_uuid = digitalocean_vpc.main.id

  # Use SSH keys for secure access
  ssh_keys = ["your-ssh-key-fingerprint-or-id"] # Replace with your actual SSH key

  # User data for initial provisioning (cloud-init)
  user_data = templatefile("${path.module}/scripts/bootstrap.sh", {
    db_host     = digitalocean_database_cluster.woocommerce_db.host
    db_port     = digitalocean_database_cluster.woocommerce_db.port
    db_name     = digitalocean_database_cluster.woocommerce_db.database[0].name
    db_user     = digitalocean_database_cluster.woocommerce_db.username
    db_password = digitalocean_database_cluster.woocommerce_db.password
    domain_name = "your-domain.com" # Replace with your actual domain
  })

  # Add Droplets to the firewall's internal rules
  provisioner "remote-exec" {
    inline = [
      "sudo ufw allow from ${digitalocean_vpc.main.ip_range} to any port 80,443",
      "sudo ufw allow from ${digitalocean_vpc.main.ip_range} to any port 22",
      "sudo ufw allow from ${digitalocean_loadbalancer.main.ip} to any port 80,443", # Allow LB access
      "sudo ufw enable"
    ]
    connection {
      type        = "ssh"
      user        = "root" # Or your default user
      private_key = file("~/.ssh/id_rsa") # Path to your private SSH key
      host        = self.ipv4_address
      timeout     = "5m"
    }
  }

  # Associate Droplets with the Load Balancer
  lifecycle {
    create_before_destroy = true
  }
}

# Associate Droplets with the Load Balancer
resource "digitalocean_loadbalancer_droplet" "app_lb_association" {
  for_each = toset([for droplet in digitalocean_droplet.app_server : droplet.id])

  loadbalancer_id = digitalocean_loadbalancer.main.id
  droplet_id      = each.value
}

# Update firewall to include Droplet IDs
resource "digitalocean_firewall" "woocommerce_fw" {
  # ... (previous firewall configuration) ...

  # Dynamically add Droplet IDs to inbound rules for internal communication
  inbound_rule {
    protocol    = "tcp"
    ports       = ["0-65535"]
    sources {
      droplet_ids = [for droplet in digitalocean_droplet.app_server : droplet.id]
      load_balancer_ids = [digitalocean_loadbalancer.main.id]
      vpc_ids = [digitalocean_vpc.main.id]
    }
  }
  inbound_rule {
    protocol    = "udp"
    ports       = ["0-65535"]
    sources {
      droplet_ids = [for droplet in digitalocean_droplet.app_server : droplet.id]
      load_balancer_ids = [digitalocean_loadbalancer.main.id]
      vpc_ids = [digitalocean_vpc.main.id]
    }
  }
  inbound_rule {
    protocol    = "icmp"
    sources {
      droplet_ids = [for droplet in digitalocean_droplet.app_server : droplet.id]
      load_balancer_ids = [digitalocean_loadbalancer.main.id]
      vpc_ids = [digitalocean_vpc.main.id]
    }
  }
}

output "app_server_ips" {
  description = "Public IP addresses of the application servers."
  value       = [for droplet in digitalocean_droplet.app_server : droplet.ipv4_address]
}

The user_data script (scripts/bootstrap.sh) is critical. It will run on Droplet boot, installing Nginx, PHP, and necessary WordPress/WooCommerce dependencies, and configuring them to connect to the managed database. It also sets up the domain name for Nginx virtual hosts.

scripts/bootstrap.sh – Droplet Bootstrapping Script

#!/bin/bash
set -euo pipefail

# Variables passed from Terraform
DB_HOST="${db_host}"
DB_PORT="${db_port}"
DB_NAME="${db_name}"
DB_USER="${db_user}"
DB_PASSWORD="${db_password}"
DOMAIN_NAME="${domain_name}"

# Update package lists and install essential packages
apt-get update -y
apt-get install -y nginx php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip unzip curl wget

# Configure Nginx
cat <<EOF > /etc/nginx/sites-available/woocommerce
server {
    listen 80;
    server_name ${DOMAIN_NAME} www.${DOMAIN_NAME};
    root /var/www/html/wordpress; # Assuming WordPress is installed here
    index index.php index.html index.htm;

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

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

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

    # Caching for static assets (optional but recommended)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
        expires max;
        log_not_found off;
    }
}
EOF

# Enable the Nginx site and remove default
ln -sf /etc/nginx/sites-available/woocommerce /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default

# Restart Nginx
systemctl restart nginx

# Configure PHP-FPM (adjust settings for performance/security)
# Example: Increase memory limit, max execution time
sed -i 's/memory_limit = .*/memory_limit = 256M/' /etc/php/8.1/fpm/php.ini # Adjust PHP version
sed -i 's/max_execution_time = .*/max_execution_time = 300/' /etc/php/8.1/fpm/php.ini # Adjust PHP version
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 64M/' /etc/php/8.1/fpm/php.ini # Adjust PHP version
sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php/8.1/fpm/php.ini # Adjust PHP version

# Restart PHP-FPM
systemctl restart php8.1-fpm # Adjust PHP version

# Install WordPress and WooCommerce (example: using WP-CLI)
# Ensure WP-CLI is installed or download it
# curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
# chmod +x wp-cli.phar
# mv wp-cli.phar /usr/local/bin/wp

# Create WordPress directory and set permissions
mkdir -p /var/www/html/wordpress
chown www-data:www-data /var/www/html/wordpress

# Download WordPress (if not already present)
if [ ! -f /var/www/html/wordpress/wp-load.php ]; then
    cd /var/www/html/wordpress
    wget https://wordpress.org/latest.tar.gz
    tar -xzf latest.tar.gz
    mv wordpress/* .
    rm -rf wordpress latest.tar.gz
    chown -R www-data:www-data .
fi

# Configure WordPress database connection
cd /var/www/html/wordpress
wp config create --dbname=${DB_NAME} --dbuser=${DB_USER} --dbpass=${DB_PASSWORD} --dbhost=${DB_HOST}:${DB_PORT} --allow-root

# Install WooCommerce plugin (example)
wp plugin install woocommerce --activate --allow-root

# Set WordPress permalinks (optional, but good practice)
wp rewrite structure '/%postname%/' --allow-root

echo "Bootstrap script finished."

Remember to replace your-ssh-key-fingerprint-or-id with your actual DigitalOcean SSH key identifier and your-domain.com with your domain. The user_data script uses wp-cli for automating WordPress and WooCommerce setup. Ensure wp-cli is installed on the base image or add its installation to the script.

Security Hardening and Final Touches

Security is paramount. Beyond network segmentation with VPCs and firewalls, we’ll ensure secure access and consider further hardening steps.

SSH Key Management

We’ve used SSH keys for Droplet access. In a production scenario, avoid using password authentication entirely. Ensure your private keys are protected and consider using a secrets management system for automated deployments.

HTTPS Configuration

For HTTPS, you have a few options:

  • Let’s Encrypt via Certbot on Droplets: The bootstrap.sh script can be extended to install and configure Certbot, automating certificate issuance and renewal. Nginx would then be configured to use these certificates.
  • DigitalOcean Load Balancer SSL Termination: Upload your SSL certificate directly to the DigitalOcean Load Balancer. This offloads SSL processing from your application servers. This requires uncommenting and configuring the digitalocean_certificate resource and referencing its ID in the Load Balancer’s forwarding rules.

For simplicity in this example, we’ve commented out the direct SSL configuration on the Load Balancer. If you choose this route, you’ll need to manage your certificate files and potentially use a separate Terraform resource for certificate management.

Database Security

The managed PostgreSQL database is already secured by being within the VPC and accessible only via its private IP. The generated credentials should be treated as secrets and rotated periodically.

Deployment Workflow

With the Terraform configuration in place, the deployment process is straightforward:

  1. Initialize Terraform: Navigate to your Terraform project directory and run terraform init. This downloads the DigitalOcean provider plugin.
  2. Plan Infrastructure: Run terraform plan to see a preview of the resources Terraform will create, modify, or destroy. Review this output carefully.
  3. Apply Infrastructure: Execute terraform apply. Terraform will prompt for confirmation before provisioning the resources on DigitalOcean.
  4. Access Your Site: Once applied, retrieve the Load Balancer’s IP address from the Terraform output and point your domain’s DNS records to it. You should then be able to access your secure WooCommerce site.
  5. Destroy Infrastructure (when needed): To tear down all provisioned resources, run terraform destroy.

This IaC approach provides a repeatable, version-controlled, and secure method for deploying and managing your WooCommerce infrastructure on DigitalOcean, enabling rapid scaling and disaster recovery.

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