Terraform by HashiCorp is the industry standard for Infrastructure as Code (IaC). It lets you define, provision, and manage cloud resources across AWS, GCP, Azure, and 2000+ providers using declarative configuration files. This guide takes you from zero to production-ready Terraform.

What is Infrastructure as Code?

IaC is the practice of managing infrastructure (servers, networks, databases) through machine-readable definition files, rather than manual processes or ad-hoc scripts. Benefits include:

  • Version control — Your entire infrastructure is in Git
  • Repeatability — Spin up identical environments every time
  • Self-documentation — The code IS the documentation
  • Automation — Integrate with CI/CD pipelines
  • Drift detection — Terraform detects manual changes

Installing Terraform

# Linux installation
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# Verify installation
terraform --version

Your First Terraform Configuration

Create a file named main.tf to provision an AWS EC2 instance:

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web" {
  ami           = "ami-0c7217cdde317cfec"
  instance_type = "t3.micro"

  tags = {
    Name = "TerraformDemo"
  }
}

Running Terraform

# Initialize — downloads providers
terraform init

# Format code
terraform fmt

# See execution plan
terraform plan

# Apply changes
terraform apply

# Destroy resources
terraform destroy

Core Concepts Deep Dive

State Management

Terraform tracks your infrastructure state in a state file (terraform.tfstate). For teams, always use remote state storage:

# backend.tf — Store state in S3 with DynamoDB locking
terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "production/network/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

Variables & Outputs

# variables.tf
variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Deployment environment"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Must be dev, staging, or prod."
  }
}

# outputs.tf
output "instance_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

# terraform.tfvars — override defaults
instance_type = "t3.medium"
environment   = "staging"

Modules (Reusable Infrastructure)

Modules are the key to DRY infrastructure. Here's how to structure them:

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
  }
}

# modules/vpc/variables.tf
variable "environment" { type = string }
variable "vpc_cidr"    { type = string }

# environments/prod/main.tf — using the module
module "vpc" {
  source      = "../../modules/vpc"
  environment = "prod"
  vpc_cidr    = "10.0.0.0/16"
}

Production-Grade Directory Structure

infrastructure/
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── ecs/
│   │   └── ...
│   └── rds/
│       └── ...
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── backend.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   └── ...
│   └── prod/
│       ├── main.tf
│       ├── backend.tf
│       └── terraform.tfvars
└── global/
    └── iam/
        └── main.tf

CI/CD Integration

Automate Terraform with GitHub Actions:

# .github/workflows/terraform.yml
name: Terraform CI/CD
on:
  push:
    branches: [main]
    paths: [infrastructure/**]

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/prod
      
      - name: Terraform Plan
        run: terraform plan -no-color
        working-directory: ./environments/prod
      
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply -auto-approve
        working-directory: ./environments/prod
Pro Tip: Always run terraform plan in CI before terraform apply to catch errors early. Use terraform Plan output as a PR comment for visibility.

Best Practices Checklist

  • ✅ Use remote state with locking (S3 + DynamoDB)
  • ✅ Isolate environments with workspaces or directories
  • ✅ Pin provider versions
  • ✅ Use modules for reusable components
  • ✅ Validate variables with validation blocks
  • ✅ Run terraform fmt in CI to enforce style
  • ✅ Use prevent_destroy on critical resources
  • ✅ Tag all resources with environment, project, owner
  • ✅ Enable versioning on state bucket
  • ✅ Use terraform_remote_state sparingly

Ready to automate more? Check out our Python Automation Scripts and CI/CD Pipeline Guide.