hashicorp/aws ~> 5.0) lets you define VPC, EC2, S3, RDS, and IAM as code — version-controlled, reproducible, and reviewable infrastructure.terraform init && terraform plan && terraform apply~> constraint — unversioned providers break on major updates.terraform plan and review output before every apply.| Resource | Terraform Type | Key Arguments | Notes |
|---|---|---|---|
| VPC | aws_vpc | cidr_block, enable_dns_hostnames | Foundation of networking |
| Subnet | aws_subnet | vpc_id, cidr_block, availability_zone | Public vs private via route table |
| Internet Gateway | aws_internet_gateway | vpc_id | Required for public subnets |
| NAT Gateway | aws_nat_gateway | subnet_id, allocation_id | For private subnet egress |
| Security Group | aws_security_group | vpc_id, ingress, egress | Stateful firewall rules |
| EC2 Instance | aws_instance | ami, instance_type, subnet_id | Use data.aws_ami for latest |
| S3 Bucket | aws_s3_bucket | bucket | Separate resources for ACL, versioning |
| RDS Instance | aws_db_instance | engine, instance_class, db_name | Use aws_db_subnet_group |
| IAM Role | aws_iam_role | assume_role_policy | Attach policies separately |
| Key Pair | aws_key_pair | public_key | For SSH access to EC2 |
| S3 Backend | backend "s3" | bucket, key, dynamodb_table | State storage + locking |
START
├── First time setting up Terraform?
│ ├── YES → Start with S3 backend + VPC module (Step 1-3)
│ └── NO ↓
├── Need full VPC with public/private subnets?
│ ├── YES → Use terraform-aws-modules/vpc (saves 100+ lines)
│ └── NO ↓
├── Single server or container workload?
│ ├── Single server → EC2 with Security Group
│ ├── Containers → ECS/Fargate with ALB
│ └── NO ↓
├── Need a database?
│ ├── YES → RDS in private subnet
│ └── NO ↓
└── DEFAULT → VPC + EC2 + S3 (full stack)
Set up remote state before anything else. [src3]
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
backend "s3" {
bucket = "my-terraform-state-123456"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = { Environment = var.environment, ManagedBy = "terraform" }
}
}
Verify: terraform init → Successfully configured the backend "s3"!
Use the community VPC module. [src5]
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
name = "${var.project_name}-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
}
Verify: terraform plan → shows VPC resources
Deploy a server with security group. [src2]
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter { name = "name", values = ["al2023-ami-*-x86_64"] }
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = module.vpc.public_subnets[0]
}
Verify: terraform apply → instance running
Review plan and apply. [src2]
terraform plan -out=tfplan
terraform apply tfplan
terraform output
Verify: terraform state list → shows all created resources
# Input: Terraform configuration
# Output: VPC, EC2, RDS PostgreSQL in private subnet
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db"
subnet_ids = module.vpc.private_subnets
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-db"
engine = "postgres"
engine_version = "16.3"
instance_class = "db.t4g.micro"
allocated_storage = 20
storage_encrypted = true
db_name = "appdb"
username = "dbadmin"
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = 7
deletion_protection = var.environment == "prod"
}
# Input: Domain name, ACM certificate ARN
# Output: S3 bucket + CloudFront distribution
resource "aws_s3_bucket" "website" {
bucket = "www.${var.domain_name}"
}
resource "aws_cloudfront_distribution" "website" {
enabled = true
default_root_object = "index.html"
aliases = [var.domain_name]
origin {
domain_name = aws_s3_bucket.website.bucket_regional_domain_name
origin_id = "S3-${aws_s3_bucket.website.id}"
origin_access_control_id = aws_cloudfront_origin_access_control.website.id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.website.id}"
viewer_protocol_policy = "redirect-to-https"
forwarded_values { query_string = false; cookies { forward = "none" } }
}
viewer_certificate {
acm_certificate_arn = var.acm_certificate_arn
ssl_support_method = "sni-only"
}
restrictions { geo_restriction { restriction_type = "none" } }
}
# ❌ BAD — no backend block = local state, no locking
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
# ✅ GOOD — remote state with encryption and locking
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# ❌ BAD — hardcoded AMI and subnet
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
subnet_id = "subnet-abc123"
}
# ✅ GOOD — dynamic lookup
data "aws_ami" "al" {
most_recent = true
owners = ["amazon"]
filter { name = "name"; values = ["al2023-ami-*-x86_64"] }
}
resource "aws_instance" "web" {
ami = data.aws_ami.al.id
subnet_id = module.vpc.public_subnets[0]
}
# ❌ BAD — 2000 lines in one main.tf
# ✅ GOOD — backend.tf, network.tf, compute.tf, storage.tf, variables.tf, outputs.tf
lifecycle { prevent_destroy = true }. [src3]encrypted = true everywhere. [src6]data "aws_ami" with filters. [src2]# Initialize and download providers
terraform init
# Preview changes
terraform plan -out=tfplan
# Apply saved plan
terraform apply tfplan
# List resources in state
terraform state list
# Show resource details
terraform state show aws_instance.web
# Validate syntax
terraform validate
# Format files
terraform fmt -recursive
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Terraform 1.9+ | Current | None | HCL improvements |
| Terraform 1.6–1.8 | Supported | terraform test GA | — |
| AWS Provider 5.x | Current | S3 bucket args split | Use separate aws_s3_bucket_* resources |
| AWS Provider 4.x | Deprecated | — | Change acl to aws_s3_bucket_acl |
| OpenTofu 1.6+ | Active | Fork of TF 1.6 | Drop-in compatible |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Reproducible multi-resource infrastructure | One-off CLI commands | AWS CLI, bash scripts |
| Team collaboration with review workflows | Rapid prototyping | AWS Console, Pulumi |
| Multi-cloud deployments | AWS-only with CFN expertise | CloudFormation, SAM |
| Long-lived infra (VPCs, RDS, S3) | Ephemeral environments | Docker Compose |
terraform destroy is irreversible for stateful resources — back up data first and use prevent_destroy.