info@pccvdi.com Pashim Vihar, New Delhi

Infrastructure as Code with Terraform: Best Practices for Multi-Cloud Deployments

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 plan command 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.tfvars in .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.