A

Ahmed

DevOps Engineer · Software Engineer

Egyptahmed@example.comOperational
Back to Blog
AWSTerraformIaC

Infrastructure as Code: Terraform on AWS

How I use Terraform to provision and manage scalable AWS infrastructure with reusable modules and remote state.

A

Ahmed

Jan 20, 20266 min read

Managing cloud infrastructure by clicking around the AWS console works fine at the start. But the moment you need to reproduce an environment, audit a change, or roll back after an incident, you feel the pain immediately. Infrastructure as Code (IaC) with Terraform solves all three.

Why Terraform Over CloudFormation?

CloudFormation is AWS-native and well-integrated, but Terraform's provider ecosystem spans every major cloud and SaaS. Your team can manage AWS, Cloudflare DNS, GitHub repos, and Datadog monitors all in one unified workflow.

  • HCL is more readable than JSON/YAML CloudFormation templates
  • terraform plan shows a diff before applying — no surprises
  • State management gives you a source of truth for what exists
  • Modules let you build reusable infrastructure components
  • Works with AWS, GCP, Azure, Kubernetes, GitHub, and 3,000+ providers

Project Structure That Scales

A flat single-file approach breaks down fast. Here's the structure I use across production projects:

bash
infra/
├── environments/
│   ├── dev/
│   │   ├── main.tf          # calls modules
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       └── terraform.tfvars
└── modules/
    ├── vpc/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    ├── eks/
    └── rds/

Remote State with S3 + DynamoDB

Warning

Never use local state in a team environment. If two engineers run apply simultaneously without state locking, you will corrupt your infrastructure state.

hcl
# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-company-tfstate"
    key            = "prod/network/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-lock"  # state locking
  }
}
bash
# One-time setup: create the S3 bucket and DynamoDB table
aws s3api create-bucket \
  --bucket my-company-tfstate \
  --region us-east-1

aws s3api put-bucket-versioning \
  --bucket my-company-tfstate \
  --versioning-configuration Status=Enabled

aws dynamodb create-table \
  --table-name terraform-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

A Reusable VPC Module

hcl
# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(var.tags, { Name = "${var.name}-vpc" })
}

resource "aws_subnet" "public" {
  count             = length(var.azs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.cidr, 8, count.index)
  availability_zone = var.azs[count.index]
  map_public_ip_on_launch = true

  tags = { Name = "${var.name}-public-${var.azs[count.index]}" }
}

The Workflow: Plan → Review → Apply

  1. 1terraform init — downloads providers and configures backend
  2. 2terraform fmt -recursive — formats all .tf files consistently
  3. 3terraform validate — catches syntax and semantic errors
  4. 4terraform plan -out=tfplan — generates a diff, saves to file
  5. 5Code review the plan output in your PR
  6. 6terraform apply tfplan — applies the exact reviewed plan

Tip

Use terraform plan -out=tfplan and then terraform apply tfplan so the apply executes exactly what was reviewed. A plain terraform apply re-plans, which can introduce drift.

“

The highest form of infrastructure management is when your git history is the audit log of every change ever made to your cloud.