terraform-aws
Terraform AWS Infrastructure Management
Overview
Complete guide for managing AWS infrastructure as code using Terraform. This skill provides production-ready patterns for VPC networking, IAM security, state management, and common AWS resources with Terraform 1.11+ features and AWS Provider 6.x.
Keywords: Terraform, AWS, infrastructure as code, IaC, VPC, IAM, S3 backend, state locking, modules, EC2, RDS, EKS, security groups, CloudWatch
Terraform Version: 1.11+ (with S3 native locking) AWS Provider: 6.x
When to Use This Skill
- Provisioning AWS infrastructure with Terraform
- Setting up VPC networking with public/private subnets
- Configuring IAM roles, policies, and permissions
- Managing Terraform state with S3 backend
- Deploying databases (RDS, Aurora)
- Creating EKS-ready VPC configurations
- Troubleshooting Terraform plan/apply failures
- Migrating from DynamoDB to S3 native locking
- Importing existing AWS resources into Terraform
Quick Start
Basic AWS Provider Configuration
terraform {
required_version = ">= 1.11.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "production/terraform.tfstate"
region = "us-east-1"
# Native S3 locking (Terraform 1.11+)
use_lockfile = true
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
Variables Configuration
variable "aws_region" {
description = "AWS region for all resources"
type = string
default = "us-east-1"
}
variable "environment" {
description = "Environment name (dev, staging, production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "project_name" {
description = "Project name for resource naming and tagging"
type = string
}
Common Resource Patterns
VPC with Public and Private Subnets
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.project_name}-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = var.environment != "production" # Cost optimization for non-prod
enable_dns_hostnames = true
enable_dns_support = true
# VPC Flow Logs
enable_flow_log = true
create_flow_log_cloudwatch_iam_role = true
create_flow_log_cloudwatch_log_group = true
tags = {
Environment = var.environment
}
}
S3 Bucket with Encryption and Versioning
resource "aws_s3_bucket" "app_data" {
bucket = "${var.project_name}-app-data-${var.environment}"
tags = {
Name = "${var.project_name}-app-data"
Environment = var.environment
}
}
resource "aws_s3_bucket_versioning" "app_data" {
bucket = aws_s3_bucket.app_data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "app_data" {
bucket = aws_s3_bucket.app_data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "app_data" {
bucket = aws_s3_bucket.app_data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
IAM Role for EC2 with SSM Access
# IAM role for EC2 instances
resource "aws_iam_role" "ec2_app_role" {
name = "${var.project_name}-ec2-app-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
}
# Attach AWS managed policy for SSM
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
role = aws_iam_role.ec2_app_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# Custom policy for S3 access
resource "aws_iam_role_policy" "ec2_s3_access" {
name = "${var.project_name}-ec2-s3-access"
role = aws_iam_role.ec2_app_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
Resource = "${aws_s3_bucket.app_data.arn}/*"
},
{
Effect = "Allow"
Action = [
"s3:ListBucket"
]
Resource = aws_s3_bucket.app_data.arn
}
]
})
}
# Instance profile for EC2
resource "aws_iam_instance_profile" "ec2_app_profile" {
name = "${var.project_name}-ec2-app-profile"
role = aws_iam_role.ec2_app_role.name
}
RDS PostgreSQL Database
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = module.vpc.private_subnets
tags = {
Name = "${var.project_name}-db-subnet-group"
}
}
resource "aws_security_group" "rds" {
name = "${var.project_name}-rds-sg"
description = "Security group for RDS database"
vpc_id = module.vpc.vpc_id
ingress {
description = "PostgreSQL from VPC"
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-db-${var.environment}"
engine = "postgres"
engine_version = "16.3"
instance_class = var.environment == "production" ? "db.t3.medium" : "db.t3.micro"
allocated_storage = var.environment == "production" ? 100 : 20
storage_type = "gp3"
db_name = var.db_name
username = var.db_username
password = var.db_password # Use AWS Secrets Manager in production!
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = var.environment == "production" ? 30 : 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
deletion_protection = var.environment == "production"
skip_final_snapshot = var.environment != "production"
final_snapshot_identifier = var.environment == "production" ? "${var.project_name}-db-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" : null
tags = {
Name = "${var.project_name}-db"
Environment = var.environment
}
}
State Management
S3 Backend with Native Locking (Terraform 1.11+)
Recommended approach - No DynamoDB required!
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "production/terraform.tfstate"
region = "us-east-1"
# Enable S3 native locking (Terraform 1.11+)
use_lockfile = true
# Encryption
encrypt = true
# Optional: Use KMS for encryption
# kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abcd1234-..."
}
}
Legacy: S3 + DynamoDB Locking
For Terraform < 1.11:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "production/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
See references/state-management.md for complete state management patterns.
Module Usage Patterns
Using Community Modules
# VPC Module
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.project_name}-vpc"
cidr = var.vpc_cidr
azs = var.availability_zones
private_subnets = var.private_subnet_cidrs
public_subnets = var.public_subnet_cidrs
enable_nat_gateway = true
enable_vpn_gateway = false
}
# EKS Module
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "${var.project_name}-eks"
cluster_version = "1.30"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
eks_managed_node_groups = {
main = {
min_size = 2
max_size = 10
desired_size = 3
instance_types = ["t3.medium"]
capacity_type = "ON_DEMAND"
}
}
}
Creating Custom Modules
modules/
└── application/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.md
# Using custom module
module "application" {
source = "./modules/application"
name = "${var.project_name}-app"
environment = var.environment
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
}
Common Workflows
Initialize Terraform
# Initialize backend and download providers
terraform init
# Migrate state to new backend
terraform init -migrate-state
# Reconfigure backend
terraform init -reconfigure
Plan and Apply Changes
# See what will change
terraform plan -out=tfplan
# Apply changes
terraform apply tfplan
# Auto-approve (use with caution!)
terraform apply -auto-approve
# Target specific resource
terraform apply -target=aws_instance.web
Import Existing Resources
# Import VPC
terraform import aws_vpc.main vpc-12345678
# Import S3 bucket
terraform import aws_s3_bucket.app_data my-bucket-name
# Import RDS instance
terraform import aws_db_instance.main my-db-instance
Workspace Management
# List workspaces
terraform workspace list
# Create workspace
terraform workspace new staging
# Switch workspace
terraform workspace select production
# Show current workspace
terraform workspace show
Detailed Documentation
For comprehensive guides on specific topics:
-
VPC Networking: references/vpc-networking.md
- VPC module configuration
- Public/private/intra subnets
- NAT Gateway patterns
- VPC endpoints
- Security groups and NACLs
- EKS-ready VPC setup
-
IAM Security: references/iam-security.md
- IAM roles and policies
- Assume role policies
- Service-linked roles
- Cross-account access
- Permission boundaries
- Policy best practices
-
State Management: references/state-management.md
- S3 backend configuration
- Native locking vs DynamoDB
- State encryption
- Workspace strategies
- Import and migration
- State manipulation
Common Issues and Solutions
| Issue | Cause | Fix |
|---|---|---|
| State lock timeout | Orphaned lock file | terraform force-unlock <lock-id> |
| Provider version conflict | Incompatible versions | Update required_providers block |
| Cycle in resource dependencies | Circular reference | Use depends_on or split resources |
| Resource already exists | Resource created outside Terraform | Use terraform import |
| Insufficient permissions | Missing IAM permissions | Add required IAM policies |
| State drift detected | Manual changes in AWS | Review with terraform plan, then apply or import |
Best Practices
Resource Naming
# Use consistent naming pattern
resource "aws_instance" "web" {
# Format: {project}-{resource}-{environment}
tags = {
Name = "${var.project_name}-web-${var.environment}"
}
}
Environment-Specific Configuration
# Use conditionals for environment differences
resource "aws_db_instance" "main" {
instance_class = var.environment == "production" ? "db.r5.large" : "db.t3.micro"
backup_retention_period = var.environment == "production" ? 30 : 7
deletion_protection = var.environment == "production"
}
Sensitive Data Management
# Never hardcode secrets!
variable "db_password" {
description = "Database master password"
type = string
sensitive = true
}
# Use AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "production/db/password"
}
resource "aws_db_instance" "main" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
Default Tags
provider "aws" {
region = var.aws_region
# All resources get these tags automatically
default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
CostCenter = var.cost_center
}
}
}
Quick Reference Commands
# Initialization
terraform init # Initialize working directory
terraform init -upgrade # Upgrade providers to latest version
# Planning
terraform plan # Show execution plan
terraform plan -out=tfplan # Save plan to file
terraform plan -destroy # Plan resource destruction
# Applying
terraform apply # Apply changes
terraform apply tfplan # Apply saved plan
terraform destroy # Destroy all resources
# State Management
terraform state list # List resources in state
terraform state show <resource> # Show resource details
terraform state rm <resource> # Remove resource from state
terraform state mv <src> <dest> # Move/rename resource in state
# Validation
terraform fmt # Format HCL files
terraform fmt -recursive # Format all .tf files
terraform validate # Validate configuration
terraform validate -json # JSON output for CI/CD
# Outputs
terraform output # Show all outputs
terraform output <name> # Show specific output
terraform output -json # JSON output
# Workspaces
terraform workspace list # List workspaces
terraform workspace new <name> # Create workspace
terraform workspace select <name> # Switch workspace
terraform workspace delete <name> # Delete workspace
Production Deployment Checklist
- S3 backend configured with encryption
- State locking enabled (native S3 or DynamoDB)
- Remote state shared among team members
- Provider versions pinned in
required_providers - Variables defined with validation rules
- Sensitive variables marked as
sensitive = true - Secrets stored in AWS Secrets Manager (not hardcoded)
- Default tags configured on provider
- Resource naming follows consistent pattern
- VPC using private subnets for sensitive resources
- Security groups follow least privilege principle
- IAM roles using principle of least privilege
- CloudWatch logging enabled for critical resources
- Backup and retention policies configured
-
terraform fmtandterraform validatepass - State file stored in version-controlled S3 bucket
-
.terraform/and*.tfstatein.gitignore
Terraform Version: 1.11+ AWS Provider: 6.x Best Practices: Following AWS Well-Architected Framework Last Updated: November 2025