• 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 Shopify Clusters on OVH Using Terraform

Infrastructure as Code: Provisioning Secure Shopify Clusters on OVH Using Terraform

OVHcloud Provider Configuration for Terraform

To provision resources on OVHcloud using Terraform, we first need to configure the OVHcloud provider. This involves obtaining API credentials and specifying them in your Terraform configuration. It’s crucial to manage these credentials securely, ideally using environment variables or a dedicated secrets management system rather than hardcoding them directly into your Terraform files.

The OVHcloud provider requires the following environment variables to be set:

  • OVH_ENDPOINT: The API endpoint for your region (e.g., ovh-eu, ovh-us, ovh-ca).
  • OVH_APPLICATION_KEY: Your OVHcloud API application key.
  • OVH_APPLICATION_SECRET: Your OVHcloud API application secret.
  • OVH_CONSUMER_KEY: Your OVHcloud API consumer key.

Here’s how you would typically define the provider in your main.tf file. Note that we are not explicitly setting the credentials here, relying on environment variables for security.

terraform {
  required_providers {
    ovh = {
      source  = "ovh/ovh"
      version = "~> 1.0" # Specify a version constraint
    }
  }
}

provider "ovh" {
  # Credentials will be read from environment variables:
  # OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY
  # For example: export OVH_ENDPOINT="ovh-eu"
}

Designing the Shopify Cluster Architecture

A secure Shopify cluster on OVH typically involves several components: a load balancer, multiple web servers (running Nginx and PHP-FPM), a robust database (e.g., Percona XtraDB Cluster or MariaDB Galera Cluster), and potentially a caching layer (like Redis or Memcached). For this example, we’ll focus on provisioning the core web tier and a managed database service.

We’ll use OVHcloud’s Public Cloud Instances for the web servers and their managed PostgreSQL service for the database. Security best practices dictate using private networks (VPC) for inter-instance communication and restricting public access to only the load balancer.

Terraform Module for Web Servers

Let’s define a reusable Terraform module for our web servers. This module will create an instance, configure networking, and set up basic security groups.

Create a directory named modules/webserver and add the following files:

modules/webserver/main.tf:

resource "ovh_compute_instance" "shopify_web" {
  name          = "${var.environment}-shopify-web-${count.index}"
  image         = "ubuntu_20_04" # Or your preferred OS image
  flavor        = var.instance_flavor
  region        = var.region
  network_id    = var.private_network_id
  ssh_key_names = [var.ssh_key_name]
  user_data     = file("${path.module}/scripts/setup_webserver.sh")

  count = var.instance_count

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Environment = var.environment
    Role        = "shopify-web"
  }
}

resource "ovh_compute_instance_public_ip" "shopify_web_public_ip" {
  count = var.assign_public_ip ? var.instance_count : 0
  instance_id = ovh_compute_instance.shopify_web[count.index].id
}

output "webserver_ips" {
  description = "Public IPs of the web servers"
  value       = [for i, ip in ovh_compute_instance_public_ip.shopify_web_public_ip : ip.address]
}

output "webserver_private_ips" {
  description = "Private IPs of the web servers"
  value       = [for instance in ovh_compute_instance.shopify_web : instance.private_ip]
}

modules/webserver/variables.tf:

variable "environment" {
  description = "Deployment environment (e.g., prod, staging)"
  type        = string
}

variable "region" {
  description = "OVHcloud region for deployment"
  type        = string
}

variable "instance_flavor" {
  description = "Flavor of the instances (e.g., 's1-2', 'c2-7')"
  type        = string
}

variable "instance_count" {
  description = "Number of web server instances"
  type        = number
  default     = 2
}

variable "private_network_id" {
  description = "ID of the private network (VPC) to attach instances to"
  type        = string
}

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

variable "assign_public_ip" {
  description = "Whether to assign a public IP to each instance"
  type        = bool
  default     = false # Typically false, managed by LB
}

modules/webserver/scripts/setup_webserver.sh (This is a simplified example; a production setup would be more robust):

#!/bin/bash
set -euxo pipefail

# Update packages and install Nginx, PHP-FPM, and necessary extensions
apt-get update -y
apt-get install -y nginx php-fpm php-mysql php-mbstring php-xml php-curl php-zip unzip

# Configure Nginx
cat < /etc/nginx/sites-available/shopify
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html; # Your Shopify root directory
    index index.php index.html index.htm;

    server_name _; # Catch all

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

    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 hidden files
    location ~ /\.ht {
        deny all;
    }
}
EOF

# Remove default Nginx site
rm /etc/nginx/sites-enabled/default

# Enable the new Shopify site
ln -s /etc/nginx/sites-available/shopify /etc/nginx/sites-enabled/shopify

# Restart Nginx and PHP-FPM
systemctl restart nginx
systemctl restart php8.1-fpm # Adjust PHP version if needed

# Basic security hardening (e.g., firewall rules - requires ufw to be installed)
# apt-get install -y ufw
# ufw allow ssh
# ufw allow http
# ufw allow https
# ufw enable

Provisioning the Database

For a production Shopify cluster, a managed database service is highly recommended. OVHcloud offers managed PostgreSQL. We’ll provision a PostgreSQL instance within our private network.

resource "ovh_db_instance" "shopify_db" {
  engine        = "postgresql"
  version       = "13" # Specify desired PostgreSQL version
  plan_name     = "ESSENTIAL" # Or "PROFESSIONAL" for higher performance/availability
  region        = var.region
  network_type  = "private" # Crucial for security
  name          = "${var.environment}-shopify-db"
  description   = "PostgreSQL for Shopify cluster"
  storage_size  = 50 # GB
  storage_type  = "SSD"
  public        = false # Ensure it's not publicly accessible

  # For ESSENTIAL plan, replicas are not available. For PROFESSIONAL, consider replicas.
  # replicas = 1 # Only for PROFESSIONAL plan

  tags = {
    Environment = var.environment
    Role        = "shopify-db"
  }
}

output "db_connection_info" {
  description = "Database connection details"
  value = {
    host     = ovh_db_instance.shopify_db.hostname
    port     = ovh_db_instance.shopify_db.port
    username = ovh_db_instance.shopify_db.username
    password = ovh_db_instance.shopify_db.password # Handle password securely!
  }
  sensitive = true # Mark as sensitive to prevent logging
}

Load Balancer Configuration

A load balancer is essential for distributing traffic and providing a single entry point. OVHcloud’s Public Cloud Load Balancer can be provisioned via Terraform. We’ll configure it to forward HTTP/HTTPS traffic to our web servers.

resource "ovh_cloud_load_balancer" "shopify_lb" {
  name        = "${var.environment}-shopify-lb"
  description = "Load balancer for Shopify cluster"
  region      = var.region
  public_network_enabled = true # Make it accessible from the internet

  # Define frontend for HTTP
  frontend {
    name = "http"
    port = 80
    default_backend_id = ovh_cloud_load_balancer_backend.shopify_web_backend.id
    protocol = "http"
  }

  # Define frontend for HTTPS (if SSL is terminated at LB)
  # frontend {
  #   name = "https"
  #   port = 443
  #   default_backend_id = ovh_cloud_load_balancer_backend.shopify_web_backend.id
  #   ssl_certificate = file("path/to/your/certificate.pem") # Load your SSL cert
  #   ssl_private_key = file("path/to/your/private_key.pem") # Load your private key
  #   protocol = "https"
  # }
}

resource "ovh_cloud_load_balancer_backend" "shopify_web_backend" {
  name = "shopify-web-backend"
  port = 80 # Port Nginx listens on
  protocol = "http"

  # Health check configuration
  health_check {
    path = "/"
    port = 80
    interval = 5000 # milliseconds
    timeout = 1000 # milliseconds
    method = "GET"
    status = 200
  }

  # Add web servers to the backend pool
  dynamic "load_balancer_servers" {
    for_each = ovh_compute_instance.shopify_web # Reference instances from the webserver module
    content {
      address = load_balancer_servers.value.private_ip
      status  = "UP" # Initial status, health checks will manage this
      port    = 80
    }
  }
}

output "load_balancer_ip" {
  description = "Public IP address of the load balancer"
  value       = ovh_cloud_load_balancer.shopify_lb.public_ip
}

Networking and Security Groups

Proper network segmentation and firewall rules are paramount for security. We’ll create a private network (VPC) and ensure instances only communicate within this network, with the load balancer being the only public-facing component.

resource "ovh_compute_network_private" "shopify_vpc" {
  name       = "${var.environment}-shopify-vpc"
  region     = var.region
  detach     = false # Keep network even if no instances are attached
  vlan_id    = 2000 # Choose an available VLAN ID
  description = "Private network for Shopify cluster"
}

# Security group for web servers: Allow traffic from LB and within VPC
resource "ovh_compute_security_group" "shopify_web_sg" {
  name        = "${var.environment}-shopify-web-sg"
  region      = var.region
  description = "Security group for Shopify web servers"
  network_id  = ovh_compute_network_private.shopify_vpc.id
}

resource "ovh_compute_security_group_rule" "web_allow_http_from_lb" {
  security_group_id = ovh_compute_security_group.shopify_web_sg.id
  direction         = "in"
  protocol          = "tcp"
  port_min          = 80
  port_max          = 80
  remote_ip_prefix  = ovh_cloud_load_balancer.shopify_lb.public_ip # Restrict to LB IP
  description       = "Allow HTTP from Load Balancer"
}

resource "ovh_compute_security_group_rule" "web_allow_ssh_from_trusted" {
  security_group_id = ovh_compute_security_group.shopify_web_sg.id
  direction         = "in"
  protocol          = "tcp"
  port_min          = 22
  port_max          = 22
  remote_ip_prefix  = "YOUR_TRUSTED_IP_RANGE/32" # e.g., "192.168.1.0/24" or your office IP
  description       = "Allow SSH from trusted network"
}

resource "ovh_compute_security_group_rule" "web_allow_internal_traffic" {
  security_group_id = ovh_compute_security_group.shopify_web_sg.id
  direction         = "in"
  protocol          = "all" # Allow all internal traffic within the VPC
  port_min          = 0
  port_max          = 65535
  remote_ip_prefix  = ovh_compute_network_private.shopify_vpc.cidr # Use VPC CIDR
  description       = "Allow all internal traffic within VPC"
}

# Security group for database: Allow traffic only from web servers
resource "ovh_compute_security_group" "shopify_db_sg" {
  name        = "${var.environment}-shopify-db-sg"
  region      = var.region
  description = "Security group for Shopify database"
  network_id  = ovh_compute_network_private.shopify_vpc.id
}

resource "ovh_compute_security_group_rule" "db_allow_postgres_from_web" {
  security_group_id = ovh_compute_security_group.shopify_db_sg.id
  direction         = "in"
  protocol          = "tcp"
  port_min          = 5432 # Default PostgreSQL port
  port_max          = 5432
  remote_ip_prefix  = ovh_compute_network_private.shopify_vpc.cidr # Allow from VPC
  description       = "Allow PostgreSQL from within VPC"
}

# Associate security groups with instances
# This requires modifying the ovh_compute_instance resource in the webserver module
# to include: security_group_ids = [ovh_compute_security_group.shopify_web_sg.id]
# And for the database, if it were an instance:
# security_group_ids = [ovh_compute_security_group.shopify_db_sg.id]
# Note: Managed DBs might not directly support SG association in the same way.
# Network ACLs or specific DB security settings would apply.
# For OVH Managed DBs, network type 'private' and ensuring it's not public is key.

Important Security Note: The remote_ip_prefix for SSH access should be as restrictive as possible. Avoid using 0.0.0.0/0. For the database, restricting access to only the VPC CIDR is crucial. If using managed services, consult OVHcloud’s documentation for specific network access control mechanisms.

Putting It All Together: Root Module

Now, let’s define the root module (e.g., in main.tf in your project root) that orchestrates these resources.

# main.tf

# --- Variables ---
variable "region" {
  description = "OVHcloud region"
  type        = string
  default     = "GRA" # Example: Gravelines, France
}

variable "environment" {
  description = "Deployment environment"
  type        = string
  default     = "staging"
}

variable "web_instance_flavor" {
  description = "Flavor for web server instances"
  type        = string
  default     = "s1-2" # Example: 2 vCPU, 4 GB RAM
}

variable "web_instance_count" {
  description = "Number of web server instances"
  type        = number
  default     = 3
}

variable "ssh_key_name" {
  description = "Name of the SSH key registered in OVHcloud"
  type        = string
  # Example: "my-prod-key" - Ensure this key exists in your OVHcloud account
}

# --- Networking ---
resource "ovh_compute_network_private" "shopify_vpc" {
  name       = "${var.environment}-shopify-vpc"
  region     = var.region
  detach     = false
  vlan_id    = 2000
  description = "Private network for Shopify cluster"
}

# --- Modules ---
module "webservers" {
  source            = "./modules/webserver"
  environment       = var.environment
  region            = var.region
  instance_flavor   = var.web_instance_flavor
  instance_count    = var.web_instance_count
  private_network_id = ovh_compute_network_private.shopify_vpc.id
  ssh_key_name      = var.ssh_key_name
  assign_public_ip  = false # Let LB handle public access
}

# --- Database ---
resource "ovh_db_instance" "shopify_db" {
  engine        = "postgresql"
  version       = "13"
  plan_name     = "ESSENTIAL" # Or "PROFESSIONAL"
  region        = var.region
  network_type  = "private"
  name          = "${var.environment}-shopify-db"
  description   = "PostgreSQL for Shopify cluster"
  storage_size  = 50
  storage_type  = "SSD"
  public        = false
  tags = {
    Environment = var.environment
    Role        = "shopify-db"
  }
}

# --- Load Balancer ---
resource "ovh_cloud_load_balancer" "shopify_lb" {
  name        = "${var.environment}-shopify-lb"
  description = "Load balancer for Shopify cluster"
  region      = var.region
  public_network_enabled = true

  frontend {
    name = "http"
    port = 80
    default_backend_id = ovh_cloud_load_balancer_backend.shopify_web_backend.id
    protocol = "http"
  }
  # Add HTTPS frontend if needed
}

resource "ovh_cloud_load_balancer_backend" "shopify_web_backend" {
  name = "shopify-web-backend"
  port = 80
  protocol = "http"

  health_check {
    path = "/"
    port = 80
    interval = 5000
    timeout = 1000
    method = "GET"
    status = 200
  }

  # Dynamically add servers from the webserver module
  load_balancer_servers {
    address = module.webservers.webserver_private_ips[0] # Example for first server
    port    = 80
  }
  load_balancer_servers {
    address = module.webservers.webserver_private_ips[1] # Example for second server
    port    = 80
  }
  # Add more servers based on module.webservers.webserver_private_ips if instance_count > 2
  # A more robust approach would use a for_each loop if the LB resource supported it directly for servers.
  # As of current OVH provider, dynamic blocks for servers are not directly supported in the same way.
  # You might need to manually list them or use a null_resource with a provisioner for complex dynamic additions.
  # For simplicity here, we'll list them explicitly for a fixed count.
  # If instance_count is dynamic, consider a data source or a provisioner.
}

# --- Outputs ---
output "load_balancer_ip" {
  description = "Public IP address of the load balancer"
  value       = ovh_cloud_load_balancer.shopify_lb.public_ip
}

output "db_host" {
  description = "Database hostname"
  value       = ovh_db_instance.shopify_db.hostname
}

output "db_port" {
  description = "Database port"
  value       = ovh_db_instance.shopify_db.port
}

output "db_username" {
  description = "Database username"
  value       = ovh_db_instance.shopify_db.username
}

output "db_password" {
  description = "Database password"
  value       = ovh_db_instance.shopify_db.password
  sensitive   = true
}

To apply this configuration:

  • Ensure your OVHcloud API credentials are set as environment variables (OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY).
  • Ensure your SSH key is registered in your OVHcloud account and its name is correctly set in the ssh_key_name variable.
  • Run terraform init to download the OVH provider.
  • Run terraform plan to review the planned changes.
  • Run terraform apply to provision the infrastructure.

Post-Provisioning Steps

After Terraform has successfully provisioned the infrastructure, you’ll need to:

  • Configure your Shopify application to use the database credentials output by Terraform.
  • Upload your Shopify application code to the web servers (e.g., via SCP, rsync, or a CI/CD pipeline). Ensure the web root (e.g., /var/www/html) is correctly set up.
  • Configure Nginx and PHP-FPM on the web servers as needed for your specific Shopify setup. The provided setup_webserver.sh is a basic template.
  • If using HTTPS, upload your SSL certificate and key to the load balancer configuration (or manage SSL termination on the web servers).
  • Set up monitoring and alerting for your instances, load balancer, and database.

This Terraform configuration provides a solid, secure foundation for a Shopify cluster on OVHcloud, leveraging Infrastructure as Code principles for repeatability and manageability.

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 thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala