Terraform Reference: AWS Basic Infrastructure

Type: Software Reference Confidence: 0.93 Sources: 7 Verified: 2026-02-28 Freshness: 2026-02-28

TL;DR

Constraints

Quick Reference

ResourceTerraform TypeKey ArgumentsNotes
VPCaws_vpccidr_block, enable_dns_hostnamesFoundation of networking
Subnetaws_subnetvpc_id, cidr_block, availability_zonePublic vs private via route table
Internet Gatewayaws_internet_gatewayvpc_idRequired for public subnets
NAT Gatewayaws_nat_gatewaysubnet_id, allocation_idFor private subnet egress
Security Groupaws_security_groupvpc_id, ingress, egressStateful firewall rules
EC2 Instanceaws_instanceami, instance_type, subnet_idUse data.aws_ami for latest
S3 Bucketaws_s3_bucketbucketSeparate resources for ACL, versioning
RDS Instanceaws_db_instanceengine, instance_class, db_nameUse aws_db_subnet_group
IAM Roleaws_iam_roleassume_role_policyAttach policies separately
Key Pairaws_key_pairpublic_keyFor SSH access to EC2
S3 Backendbackend "s3"bucket, key, dynamodb_tableState storage + locking

Decision Tree

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)

Step-by-Step Guide

1. Configure S3 backend for remote state

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 initSuccessfully configured the backend "s3"!

2. Define VPC with subnets

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

3. Launch EC2 instance

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

4. Apply and verify

Review plan and apply. [src2]

terraform plan -out=tfplan
terraform apply tfplan
terraform output

Verify: terraform state list → shows all created resources

Code Examples

HCL: VPC + EC2 + RDS stack

# 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"
}

HCL: S3 + CloudFront static website

# 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" } }
}

Anti-Patterns

Wrong: Local state with no backend

# ❌ BAD — no backend block = local state, no locking
terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

Correct: S3 backend with DynamoDB locking

# ✅ 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
  }
}

Wrong: Hardcoded values

# ❌ BAD — hardcoded AMI and subnet
resource "aws_instance" "web" {
  ami       = "ami-0c55b159cbfafe1f0"
  subnet_id = "subnet-abc123"
}

Correct: Variables and data sources

# ✅ 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]
}

Wrong: Monolithic single file

# ❌ BAD — 2000 lines in one main.tf

Correct: Modular file structure

# ✅ GOOD — backend.tf, network.tf, compute.tf, storage.tf, variables.tf, outputs.tf

Common Pitfalls

Diagnostic Commands

# 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 History & Compatibility

VersionStatusBreaking ChangesMigration Notes
Terraform 1.9+CurrentNoneHCL improvements
Terraform 1.6–1.8Supportedterraform test GA
AWS Provider 5.xCurrentS3 bucket args splitUse separate aws_s3_bucket_* resources
AWS Provider 4.xDeprecatedChange acl to aws_s3_bucket_acl
OpenTofu 1.6+ActiveFork of TF 1.6Drop-in compatible

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Reproducible multi-resource infrastructureOne-off CLI commandsAWS CLI, bash scripts
Team collaboration with review workflowsRapid prototypingAWS Console, Pulumi
Multi-cloud deploymentsAWS-only with CFN expertiseCloudFormation, SAM
Long-lived infra (VPCs, RDS, S3)Ephemeral environmentsDocker Compose

Important Caveats

Related Units