Infrastructure as Code (IaC) has transformed how organizations provision and manage cloud resources. Terraform, created by HashiCorp, has emerged as the dominant multi-cloud IaC tool, supporting Azure, AWS, GCP, Oracle Cloud, and dozens of other providers with a single configuration language. This guide covers production-grade Terraform practices for organizations managing multi-cloud infrastructure.
Why Terraform for Multi-Cloud?
Unlike cloud-specific tools (AWS CloudFormation, Azure Bicep, GCP Deployment Manager), Terraform uses a unified language — HashiCorp Configuration Language (HCL) — to manage resources across any cloud provider. This means your team learns one tool, one workflow, and one state management approach regardless of the underlying cloud.
Key Advantages
- Provider ecosystem: 3,000+ providers covering every major cloud, SaaS, and infrastructure platform
- Declarative syntax: Define the desired end state; Terraform calculates the changes needed
- Plan before apply: The
terraform plancommand shows exactly what will change before any modification is made - State management: Tracks the current state of all managed resources, enabling incremental updates and drift detection
Project Structure for Multi-Cloud
A well-organized Terraform project separates environments, modules, and provider configurations:
infrastructure/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── database/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ └── ...
│ └── production/
│ └── ...
└── providers/
├── aws.tf
├── azure.tf
└── gcp.tf
Module Design Best Practices
Reusable Networking Module
# modules/networking/variables.tf
variable "environment" {
type = string
description = "Environment name (dev, staging, production)"
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
type = list(string)
description = "List of AZs for subnet distribution"
}
# modules/networking/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.environment}-private-${var.availability_zones[count.index]}"
}
}
Module Versioning
Always version your modules. Use Git tags or a Terraform registry:
module "networking" {
source = "git::https://github.com/org/tf-modules.git//networking?ref=v2.1.0"
environment = "production"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["ap-south-1a", "ap-south-1b", "ap-south-1c"]
}
State Management
Remote Backend Configuration
Never store Terraform state locally in production. Use a remote backend with locking:
# backend.tf - AWS S3 + DynamoDB locking
terraform {
backend "s3" {
bucket = "myorg-terraform-state"
key = "production/infrastructure.tfstate"
region = "ap-south-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
# For Azure
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "myorgterraformstate"
container_name = "tfstate"
key = "production.terraform.tfstate"
}
}
State Isolation
Use separate state files per environment and per service layer. This limits the blast radius of any misconfiguration and enables independent deployment of network, compute, and database layers.
CI/CD Integration
GitHub Actions Pipeline
# .github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: environments/production
- name: Terraform Plan
run: terraform plan -out=tfplan
working-directory: environments/production
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplan
working-directory: environments/production
Drift Detection
Infrastructure drift occurs when actual cloud resources diverge from Terraform state (manual changes, console edits). Detect drift with:
# Compare real infrastructure against state
terraform plan -detailed-exitcode
# Exit code 0: No changes (no drift)
# Exit code 1: Error
# Exit code 2: Changes detected (drift found)
Run drift detection on a schedule (daily via cron or CI/CD) and alert when changes are detected.
Security Considerations
- Never commit secrets: Use
terraform.tfvarsin.gitignore. Pass secrets via environment variables or a secrets manager (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault). - Use
sensitive = true: Mark variables containing passwords or keys as sensitive to prevent them from appearing in plan output. - Policy as Code: Use Sentinel (Terraform Cloud) or OPA/Conftest to enforce policies like “no public S3 buckets” or “all VMs must use encrypted disks.”
Expert IaC Implementation
Adopting Terraform at scale requires thoughtful module design, state management, and CI/CD integration. At PCCVDI Solutions, our DevOps engineers design and implement Terraform-based infrastructure automation for organizations across Azure, AWS, Oracle Cloud, and GCP. From initial architecture design to ongoing module development and pipeline integration, we help you achieve fully automated, version-controlled infrastructure. Contact our DevOps team to modernize your infrastructure management.
