• 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 Magento 2 Clusters on Google Cloud Using Terraform

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

Terraform Project Structure for Magento 2 on GCP

A robust Infrastructure as Code (IaC) strategy for deploying complex applications like Magento 2 on Google Cloud Platform (GCP) necessitates a well-organized Terraform project. This structure facilitates maintainability, reusability, and collaboration. We’ll adopt a modular approach, separating concerns for networking, compute, databases, and application-specific configurations.

Our root module will orchestrate the deployment, calling sub-modules for distinct infrastructure components. This promotes a clear separation of concerns and allows for easier management of individual parts of the infrastructure.

Networking Module: VPC, Subnets, and Firewall Rules

Securely isolating your Magento 2 cluster begins with a well-defined Virtual Private Cloud (VPC) network. We’ll define custom subnets for different tiers (e.g., public-facing web servers, private application servers, database servers) and implement strict firewall rules to control ingress and egress traffic.

The following Terraform configuration outlines the creation of a custom VPC, a public subnet for load balancers and bastion hosts, and a private subnet for Magento application servers and the database. We’ll also define essential firewall rules for SSH, HTTP/S, and internal communication.

modules/gcp/networking/main.tf

# GCP VPC Network
resource "google_compute_network" "magento_vpc" {
  name                    = "${var.project_name}-vpc"
  auto_create_subnetworks = false
  routing_mode            = "REGIONAL"
}

# Public Subnet for Load Balancers and Bastion Hosts
resource "google_compute_subnetwork" "public_subnet" {
  name          = "${var.project_name}-public-subnet"
  ip_cidr_range = "10.0.1.0/24"
  region        = var.region
  network       = google_compute_network.magento_vpc.id
  private_ip_google_access = true # Allows instances to reach Google APIs without external IPs
}

# Private Subnet for Magento Application Servers and Database
resource "google_compute_subnetwork" "private_subnet" {
  name          = "${var.project_name}-private-subnet"
  ip_cidr_range = "10.0.2.0/24"
  region        = var.region
  network       = google_compute_network.magento_vpc.id
  private_ip_google_access = true
}

# Firewall Rule: Allow SSH from Bastion Host (or specific IP range)
resource "google_compute_firewall" "allow_ssh" {
  name    = "${var.project_name}-allow-ssh"
  network = google_compute_network.magento_vpc.name
  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
  source_ranges = ["10.0.1.0/24"] # Restrict SSH to the public subnet
  target_tags   = ["magento-server"]
}

# Firewall Rule: Allow HTTP/HTTPS from Load Balancer
resource "google_compute_firewall" "allow_http_https" {
  name    = "${var.project_name}-allow-http-https"
  network = google_compute_network.magento_vpc.name
  allow {
    protocol = "tcp"
    ports    = ["80", "443"]
  }
  source_ranges = ["0.0.0.0/0"] # Allow from anywhere, LB will handle further restrictions
  target_tags   = ["magento-web"]
}

# Firewall Rule: Allow internal communication between Magento components
resource "google_compute_firewall" "allow_internal" {
  name    = "${var.project_name}-allow-internal"
  network = google_compute_network.magento_vpc.name
  allow {
    protocol = "tcp"
    ports    = ["0-65535"]
  }
  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }
  allow {
    protocol = "icmp"
  }
  source_ranges = ["10.0.1.0/24", "10.0.2.0/24"] # Allow communication within VPC subnets
  target_tags   = ["magento-server", "magento-web", "magento-db"]
}

# Firewall Rule: Deny all other ingress traffic by default
resource "google_compute_firewall" "deny_all_ingress" {
  name    = "${var.project_name}-deny-all-ingress"
  network = google_compute_network.magento_vpc.name
  deny {
    protocol = "tcp"
    ports    = ["0-65535"]
  }
  deny {
    protocol = "udp"
    ports    = ["0-65535"]
  }
  deny {
    protocol = "icmp"
  }
  source_ranges = ["0.0.0.0/0"]
  # No target_tags means it applies to all instances, effectively a default deny
}

# Output VPC and Subnet IDs for use in other modules
output "vpc_id" {
  description = "The ID of the VPC network."
  value       = google_compute_network.magento_vpc.id
}

output "public_subnet_id" {
  description = "The ID of the public subnet."
  value       = google_compute_subnetwork.public_subnet.id
}

output "private_subnet_id" {
  description = "The ID of the private subnet."
  value       = google_compute_subnetwork.private_subnet.id
}

modules/gcp/networking/variables.tf

variable "project_name" {
  description = "The name of the project, used for naming resources."
  type        = string
}

variable "region" {
  description = "The GCP region for deploying resources."
  type        = string
}

Compute Module: Magento Application Servers (GCE Instances)

We’ll provision a managed instance group (MIG) for our Magento application servers. This provides scalability, high availability, and automated healing. Each instance will be configured with a hardened OS image, necessary software packages, and a startup script to join the cluster.

For security, instances will reside in the private subnet, accessible only via internal IP addresses. SSH access will be proxied through a bastion host or managed via Identity-Aware Proxy (IAP).

modules/gcp/compute/main.tf

# Managed Instance Group for Magento Application Servers
resource "google_compute_instance_group_manager" "magento_app_mig" {
  name               = "${var.project_name}-app-mig"
  base_instance_name = "${var.project_name}-app"
  zone               = var.zone
  target_size        = var.instance_count
  version {
    instance_template = google_compute_instance_template.magento_app_template.id
  }

  auto_healing_policies {
    health_check      = google_compute_health_check.app_health_check.id
    initial_delay_sec = 300 # Wait 5 minutes before health checking
  }

  update_policy {
    type = "PROACTIVE"
    minimal_action = "REPLACE"
    max_unavailable = 1
    max_surge = 1
  }
}

# Instance Template for Magento Application Servers
resource "google_compute_instance_template" "magento_app_template" {
  name_prefix  = "${var.project_name}-app-template-"
  machine_type = var.machine_type
  tags         = ["magento-server", "magento-app"]

  disk {
    source_image = "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20230913" # Or a custom hardened image
    auto_delete  = true
    boot         = true
  }

  network_interface {
    subnetwork = var.private_subnet_id
    network_ip = "" # Assign internal IP automatically
    access_config {
      # Ephemeral public IP is not needed for instances in private subnet
    }
  }

  metadata = {
    # User data for cloud-init or startup scripts
    startup-script = file("${path.module}/scripts/startup.sh")
  }

  service_account {
    scopes = ["cloud-platform"] # Grant broad access for simplicity, refine in production
  }

  scheduling {
    automatic_restart   = true
    on_host_maintenance = "MIGRATE"
  }

  lifecycle {
    create_before_destroy = true
  }
}

# Health Check for Application Servers
resource "google_compute_health_check" "app_health_check" {
  name                = "${var.project_name}-app-hc"
  check_interval_sec  = 30
  timeout_sec         = 5
  healthy_threshold   = 2
  unhealthy_threshold = 3

  http_health_check {
    port         = 80
    request_path = "/healthz" # A simple health check endpoint on your app
  }
}

# Output MIG name
output "app_mig_name" {
  description = "The name of the Magento application MIG."
  value       = google_compute_instance_group_manager.magento_app_mig.name
}

modules/gcp/compute/variables.tf

variable "project_name" {
  description = "The name of the project, used for naming resources."
  type        = string
}

variable "zone" {
  description = "The GCP zone for deploying resources."
  type        = string
}

variable "instance_count" {
  description = "The desired number of application instances."
  type        = number
  default     = 2
}

variable "machine_type" {
  description = "The machine type for application instances."
  type        = string
  default     = "e2-medium"
}

variable "private_subnet_id" {
  description = "The ID of the private subnet to deploy instances into."
  type        = string
}

modules/gcp/compute/scripts/startup.sh

#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status.

# Update package lists and install necessary packages
apt-get update -y
apt-get install -y \
    nginx \
    php-fpm \
    php-mysql \
    php-gd \
    php-xml \
    php-mbstring \
    php-curl \
    php-zip \
    php-intl \
    redis-server \
    unzip \
    git

# Configure Nginx (basic example, will be managed by a dedicated webserver module later)
cat <<EOF > /etc/nginx/sites-available/magento
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    root /var/www/html/public; # Magento's public 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/php7.4-fpm.sock; # Adjust PHP version as needed
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to sensitive files
    location ~* /(composer\.json|composer\.lock|\.env|\.htaccess|var/|\.git/|\.svn/|media/downloadable/|\bin/|\setup/|\update/|\magento) {
        deny all;
    }

    location /healthz {
        access_log off;
        return 200 "OK";
    }
}
EOF
ln -sf /etc/nginx/sites-available/magento /etc/nginx/sites-enabled/magento
rm /etc/nginx/sites-enabled/default # Remove default Nginx config

systemctl restart nginx
systemctl enable nginx

# Configure PHP-FPM (adjust settings for production)
sed -i 's/memory_limit = .*/memory_limit = 512M/' /etc/php/7.4/fpm/php.ini # Adjust PHP version
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 64M/' /etc/php/7.4/fpm/php.ini
sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php/7.4/fpm/php.ini
sed -i 's/max_execution_time = .*/max_execution_time = 300/' /etc/php/7.4/fpm/php.ini
sed -i 's/;daemonize = yes/daemonize = no/' /etc/php/7.4/fpm/php-fpm.conf # For systemd

systemctl restart php7.4-fpm # Adjust PHP version
systemctl enable php7.4-fpm

# Configure Redis (basic example)
systemctl start redis-server
systemctl enable redis-server

# Placeholder for Magento installation/configuration steps
# This would typically involve:
# 1. Downloading Magento
# 2. Running composer install
# 3. Setting up database connection
# 4. Running Magento setup commands (setup:install, setup:upgrade, cache:flush)
# 5. Deploying static content
# 6. Setting file permissions

echo "Magento application server setup complete."

Database Module: Cloud SQL for MySQL

For the Magento database, we’ll leverage Google Cloud SQL for MySQL. This managed service offers high availability, automated backups, and patching, significantly reducing operational overhead. We’ll configure it for optimal performance and security, placing it in the private subnet.

modules/gcp/database/main.tf

# Google Cloud SQL Instance for Magento Database
resource "google_sql_database_instance" "magento_db" {
  name             = "${var.project_name}-db"
  region           = var.region
  database_version = "MYSQL_8_0" # Or your preferred MySQL version

  settings {
    tier = var.db_tier
    ip_configuration {
      ipv4_enabled    = false # Private IP only
      private_network = var.vpc_self_link
      require_ssl     = true
    }
    backup_configuration {
      enabled            = true
      binary_log_enabled = true
      point_in_time_recovery_enabled = true
    }
    location_preference {
      zone = var.zone
    }
    # High Availability configuration
    availability_type = "REGIONAL"
    # Uncomment and configure for read replicas if needed
    # replica_configuration {
    #   failover_target = true
    # }
  }

  # Prevent accidental deletion
  deletion_protection = true
}

# Magento Database
resource "google_sql_database" "magento_database" {
  name     = "magento_db"
  instance = google_sql_database_instance.magento_db.name
  charset  = "utf8mb4"
  collation = "utf8mb4_unicode_ci"
}

# Magento Database User
resource "google_sql_user" "magento_user" {
  name     = "magento_user"
  instance = google_sql_database_instance.magento_db.name
  host     = "%" # Allow connections from any host within the VPC
  password = random_password.db_password.result
}

# Generate a random password for the database user
resource "random_password" "db_password" {
  length           = 32
  special          = true
  override_special = "_%@"
}

# Output database connection details
output "db_instance_name" {
  description = "The name of the Cloud SQL instance."
  value       = google_sql_database_instance.magento_db.name
}

output "db_name" {
  description = "The name of the Magento database."
  value       = google_sql_database.magento_database.name
}

output "db_user" {
  description = "The username for the Magento database."
  value       = google_sql_user.magento_user.name
}

output "db_password" {
  description = "The password for the Magento database user."
  value       = google_sql_user.magento_user.password
  sensitive   = true
}

output "db_private_ip_address" {
  description = "The private IP address of the Cloud SQL instance."
  value       = google_sql_database_instance.magento_db.private_ip_address
}

modules/gcp/database/variables.tf

variable "project_name" {
  description = "The name of the project, used for naming resources."
  type        = string
}

variable "region" {
  description = "The GCP region for deploying resources."
  type        = string
}

variable "zone" {
  description = "The GCP zone for deploying resources."
  type        = string
}

variable "vpc_self_link" {
  description = "The self-link of the VPC network to connect the Cloud SQL instance to."
  type        = string
}

variable "db_tier" {
  description = "The machine type for the Cloud SQL instance."
  type        = string
  default     = "db-custom-2-7680" # Example custom tier: 2 vCPU, 7.68 GB RAM
}

Load Balancing and Web Server Module

A robust load balancing strategy is crucial for distributing traffic and ensuring high availability. We’ll use a GCP Network Load Balancer (NLB) or HTTP(S) Load Balancer to distribute traffic to our Magento application servers. This module will also manage the Nginx configuration on the web-facing instances.

modules/gcp/webserver/main.tf

# Google Cloud HTTP(S) Load Balancer
resource "google_compute_backend_service" "magento_backend" {
  name                  = "${var.project_name}-backend-service"
  protocol              = "HTTP"
  port_name             = "http"
  timeout_sec           = 30
  enable_cdn            = false
  load_balancing_scheme = "EXTERNAL"

  backend {
    group = var.app_mig_self_link # Link to the MIG
    balancing_mode = "UTILIZATION"
    capacity_scaler = 1.0
  }

  health_checks = [google_compute_health_check.app_health_check.id]

  # Enable session affinity if needed for specific Magento functionalities
  # session_affinity = "CLIENT_IP"
}

# URL Map for the Load Balancer
resource "google_compute_url_map" "magento_url_map" {
  name            = "${var.project_name}-url-map"
  default_service = google_compute_backend_service.magento_backend.id
}

# Target HTTP Proxy
resource "google_compute_target_http_proxy" "magento_http_proxy" {
  name    = "${var.project_name}-http-proxy"
  url_map = google_compute_url_map.magento_url_map.id
}

# Global Forwarding Rule for HTTP
resource "google_compute_global_forwarding_rule" "magento_http_forwarding_rule" {
  name                  = "${var.project_name}-http-forwarding-rule"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL"
  port_range            = "80"
  target                = google_compute_target_http_proxy.magento_http_proxy.id
  ip_address            = google_compute_global_address.lb_ip.address
}

# Static IP Address for the Load Balancer
resource "google_compute_global_address" "lb_ip" {
  name = "${var.project_name}-lb-ip"
}

# --- HTTPS Configuration (Optional but Recommended) ---
# You would typically manage SSL certificates using Google-managed certificates
# or by uploading your own. This example omits detailed cert management for brevity.

# resource "google_compute_target_https_proxy" "magento_https_proxy" {
#   name             = "${var.project_name}-https-proxy"
#   url_map          = google_compute_url_map.magento_url_map.id
#   ssl_certificates = [google_compute_managed_ssl_certificate.magento_cert.id]
# }

# resource "google_compute_global_forwarding_rule" "magento_https_forwarding_rule" {
#   name                  = "${var.project_name}-https-forwarding-rule"
#   ip_protocol           = "TCP"
#   load_balancing_scheme = "EXTERNAL"
#   port_range            = "443"
#   target                = google_compute_target_https_proxy.magento_https_proxy.id
#   ip_address            = google_compute_global_address.lb_ip.address
# }

# resource "google_compute_managed_ssl_certificate" "magento_cert" {
#   name = "${var.project_name}-magento-cert"
#   managed {
#     domains = ["your-magento-domain.com"] # Replace with your domain
#   }
# }

# Output Load Balancer IP
output "load_balancer_ip" {
  description = "The static IP address of the load balancer."
  value       = google_compute_global_address.lb_ip.address
}

modules/gcp/webserver/variables.tf

variable "project_name" {
  description = "The name of the project, used for naming resources."
  type        = string
}

variable "app_mig_self_link" {
  description = "The self-link of the application instance group manager."
  type        = string
}

variable "app_health_check_id" {
  description = "The ID of the application health check."
  type        = string
}

Root Module: Orchestration

The root module ties everything together. It defines the GCP provider, declares variables, and calls the sub-modules to provision the complete Magento 2 cluster. This is where you’ll specify your project ID, region, zone, and other top-level configurations.

main.tf (Root Module)

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

provider "google" {
  project = var.gcp_project_id
  region  = var.region
}

# Networking Module
module "networking" {
  source       = "./modules/gcp/networking"
  project_name = var.project_name
  region       = var.region
}

# Compute Module
module "compute" {
  source          = "./modules/gcp/compute"
  project_name    = var.project_name
  zone            = var.zone
  instance_count  = var.app_instance_count
  machine_type    = var.app_machine_type
  private_subnet_id = module.networking.private_subnet_id
}

# Database Module
module "database" {
  source        = "./modules/gcp/database"
  project_name  = var.project_name
  region        = var.region
  zone          = var.zone
  vpc_self_link = module.networking.vpc_id
  db_tier       = var.db_tier
}

# Web Server / Load Balancer Module
module "webserver" {
  source               = "./modules/gcp/webserver"
  project_name         = var.project_name
  app_mig_self_link    = google_compute_instance_group_manager.magento_app_mig.self_link # Reference from compute module
  app_health_check_id  = google_compute_health_check.app_health_check.id # Reference from compute module
}

# Outputs
output "load_balancer_ip" {
  description = "The static IP address of the load balancer."
  value       = module.webserver.load_balancer_ip
}

output "db_instance_name" {
  description = "The name of the Cloud SQL instance."
  value       = module.database.db_instance_name
}

output "db_name" {
  description = "The name of the Magento database."
  value       = module.database.db_name
}

output "db_user" {
  description = "The username for the Magento database."
  value       = module.database.db_user
}

output "db_password" {
  description = "The password for the Magento database user."
  value       = module.database.db_password
  sensitive   = true
}

variables.tf (Root Module)

variable "gcp_project_id" {
  description = "Your GCP project ID."
  type        = string
}

variable "project_name" {
  description = "A short name for the project, used for resource naming."
  type        = string
  default     = "magento-prod"
}

variable "region" {
  description = "The GCP region for deploying resources."
  type        = string
  default     = "us-central1"
}

variable "zone" {
  description = "The GCP zone for deploying resources."
  type        = string
  default     = "us-central1-a"
}

variable "app_instance_count" {
  description = "The desired number of application instances."
  type        = number
  default     = 3
}

variable "app_machine_type" {
  description = "The machine type for application instances."
  type        = string
  default     = "e2-standard-2"
}

variable "db_tier" {
  description = "The machine type for the Cloud SQL instance."
  type        = string
  default     = "db-custom-4-15360" # Example custom tier: 4 vCPU, 15.36 GB RAM
}

terraform.tfvars.example (Root Module)

gcp_project_id = "your-gcp-project-id"
# project_name = "my-magento-cluster" # Optional: override default
# region       = "europe-west2"      # Optional: override default
# zone         = "europe-west2-b"     # Optional: override default
# app_instance_count = 5             # Optional: override default
# app_machine_type = "n2-standard-4" # Optional: override default
# db_tier = "db-standard-1"        # Optional: override default

Deployment and Management Workflow

With the Terraform code structured and defined, the deployment process is streamlined:

  • Initialize Terraform: Navigate to your root Terraform directory and run terraform init. This downloads the necessary provider plugins.
  • Review Plan: Execute terraform plan -var-file="terraform.tfvars.example" to see the exact resources Terraform will create, modify, or destroy. Carefully review this output.
  • Apply Configuration: Run terraform apply -var-file="terraform.tfvars.example" to provision the infrastructure. Confirm with ‘yes’ when prompted.
  • Magento Deployment: After infrastructure provisioning, you’ll need to deploy your Magento application code. This can be automated using CI/CD pipelines (e.g., Cloud Build, Jenkins) that trigger after Terraform apply, or by manually SSHing into the bastion host (if configured) and executing deployment scripts. The startup scripts in the compute module provide a basic setup, but a full Magento deployment is a separate, complex process.
  • Access Magento: Once deployed, access your Magento store via the load balancer’s IP address outputted by Terraform.
  • Destroy Infrastructure: When no longer needed, run terraform destroy -var-file="terraform.tfvars.example" to tear down all provisioned resources and avoid ongoing costs.

Security Considerations and Enhancements

The provided configuration offers a foundational secure setup. For production environments, consider these enhancements:

  • Hardened OS Images: Use custom-built, hardened OS images for your instances, minimizing attack surfaces.
  • Secrets Management: Integrate with GCP Secret Manager for database credentials, API keys, and other sensitive information instead of hardcoding or relying solely on Terraform outputs.
  • IAM Roles: Refine service account scopes to grant only the minimum necessary permissions.
  • Network Security: Implement more granular firewall rules. Consider using Private Google Access for all outbound traffic to Google APIs.
  • Web Application Firewall (WAF): Deploy Cloud Armor or a third-party WAF in front of the load balancer for protection against common web exploits.
  • Bastion Host: For enhanced security, provision a dedicated bastion host in the public subnet, restricting SSH access to it, and then allowing SSH from the bastion to the private instances.
  • Logging and Monitoring: Configure comprehensive logging (Cloud Logging) and monitoring (Cloud Monitoring) for all resources.
  • VPC Service Controls: Implement VPC Service Controls to create security perimeters around your GCP resources.
  • Database Security: Regularly rotate database passwords and enforce SSL/TLS connections.

By leveraging Terraform and GCP’s robust infrastructure services, you can provision a secure, scalable, and highly available Magento 2 cluster with a well-defined and repeatable process.

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