# Terraform Best Practices ## DRY Principles **Use Modules for Reusability** ```hcl # Bad - Repeated code resource "aws_vpc" "app1" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true tags = { Name = "app1-vpc", Environment = "prod" } } resource "aws_vpc" "app2" { cidr_block = "10.1.0.0/16" enable_dns_hostnames = true tags = { Name = "app2-vpc", Environment = "prod" } } # Good - Use module module "vpc_app1" { source = "./modules/vpc" name = "app1" cidr_block = "10.0.0.0/16" environment = "prod" } module "vpc_app2" { source = "./modules/vpc" name = "app2" cidr_block = "10.1.0.0/16" environment = "prod" } ``` **Use Locals for Repeated Values** ```hcl locals { common_tags = { Environment = var.environment ManagedBy = "Terraform" Project = var.project_name CostCenter = var.cost_center } name_prefix = "${var.project_name}-${var.environment}" # Computed locals vpc_cidr = var.environment == "production" ? "10.0.0.0/16" : "10.1.0.0/16" # Complex data structures availability_zones = slice(data.aws_availability_zones.available.names, 0, var.az_count) } resource "aws_vpc" "main" { cidr_block = local.vpc_cidr tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" }) } ``` **Use Data Sources Instead of Hardcoding** ```hcl # Bad - Hardcoded AMI resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" } # Good - Dynamic AMI lookup data "aws_ami" "amazon_linux_2" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-gp2"] } filter { name = "virtualization-type" values = ["hvm"] } } resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux_2.id instance_type = "t3.micro" } ``` **Use for_each for Multiple Similar Resources** ```hcl # Bad - Duplicated resources resource "aws_subnet" "private_1" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "us-east-1a" } resource "aws_subnet" "private_2" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" availability_zone = "us-east-1b" } # Good - Use for_each variable "private_subnets" { type = map(object({ cidr_block = string az = string })) default = { subnet1 = { cidr_block = "10.0.1.0/24", az = "us-east-1a" } subnet2 = { cidr_block = "10.0.2.0/24", az = "us-east-1b" } } } resource "aws_subnet" "private" { for_each = var.private_subnets vpc_id = aws_vpc.main.id cidr_block = each.value.cidr_block availability_zone = each.value.az tags = { Name = "${var.name}-private-${each.key}" } } ``` ## Naming Conventions **Resource Naming** ```hcl # Pattern: {resource_type}_{descriptive_name} # Good examples resource "aws_vpc" "main" {} resource "aws_subnet" "private" {} resource "aws_security_group" "web" {} resource "aws_instance" "app" {} # Avoid generic names resource "aws_vpc" "vpc" {} # Bad resource "aws_subnet" "subnet" {} # Bad resource "aws_vpc" "this" {} # Use in modules only ``` **AWS Resource Name Tags** ```hcl locals { # Pattern: {project}-{environment}-{resource}-{identifier} name_prefix = "${var.project_name}-${var.environment}" } resource "aws_vpc" "main" { cidr_block = var.cidr_block tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" }) } resource "aws_subnet" "private" { for_each = var.private_subnets vpc_id = aws_vpc.main.id cidr_block = each.value.cidr_block tags = merge(local.common_tags, { Name = "${local.name_prefix}-private-${each.key}" Type = "private" }) } resource "aws_security_group" "web" { name = "${local.name_prefix}-web-sg" vpc_id = aws_vpc.main.id tags = merge(local.common_tags, { Name = "${local.name_prefix}-web-sg" }) } ``` **Variable Naming** ```hcl # Use snake_case for all names variable "instance_type" {} # Good variable "instanceType" {} # Bad variable "InstanceType" {} # Bad # Be descriptive variable "vpc_cidr_block" {} # Good variable "cidr" {} # Too vague # Boolean variables should be questions variable "enable_nat_gateway" {} # Good variable "nat_gateway" {} # Ambiguous # Plural for lists/maps variable "availability_zones" {} # Good variable "private_subnets" {} # Good ``` **File Naming** ``` # Standard structure main.tf # Primary resource definitions variables.tf # Input variables outputs.tf # Output values versions.tf # Terraform and provider versions backend.tf # Backend configuration (optional) locals.tf # Local values (optional) data.tf # Data sources (optional) # Resource-specific files for complex modules vpc.tf subnets.tf security_groups.tf route_tables.tf ``` ## Security Best Practices **Secret Management** ```hcl # Bad - Secrets in plain text variable "db_password" { default = "SuperSecret123!" # NEVER DO THIS } # Good - Use sensitive variables variable "db_password" { description = "Database password" type = string sensitive = true # No default - must be provided } # Better - Use secrets manager data "aws_secretsmanager_secret_version" "db_password" { secret_id = "prod/db/password" } resource "aws_db_instance" "main" { password = data.aws_secretsmanager_secret_version.db_password.secret_string } ``` **Encryption at Rest** ```hcl # S3 bucket with encryption resource "aws_s3_bucket" "data" { bucket = "my-data-bucket" } resource "aws_s3_bucket_server_side_encryption_configuration" "data" { bucket = aws_s3_bucket.data.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" kms_master_key_id = aws_kms_key.s3.arn } bucket_key_enabled = true } } # EBS volume encryption resource "aws_ebs_volume" "data" { availability_zone = "us-east-1a" size = 100 encrypted = true kms_key_id = aws_kms_key.ebs.arn } # RDS encryption resource "aws_db_instance" "main" { storage_encrypted = true kms_key_id = aws_kms_key.rds.arn } ``` **Least Privilege IAM** ```hcl # Bad - Overly permissive data "aws_iam_policy_document" "bad" { statement { effect = "Allow" actions = ["*"] resources = ["*"] } } # Good - Specific permissions data "aws_iam_policy_document" "good" { statement { effect = "Allow" actions = [ "s3:GetObject", "s3:PutObject" ] resources = [ "${aws_s3_bucket.data.arn}/*" ] } statement { effect = "Allow" actions = [ "s3:ListBucket" ] resources = [ aws_s3_bucket.data.arn ] } } ``` **Network Security** ```hcl # Security group with restricted access resource "aws_security_group" "web" { name = "${var.name}-web-sg" description = "Security group for web servers" vpc_id = aws_vpc.main.id # Bad - Too permissive # ingress { # from_port = 0 # to_port = 65535 # protocol = "tcp" # cidr_blocks = ["0.0.0.0/0"] # } # Good - Specific rules ingress { description = "HTTPS from internet" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "HTTP from ALB" from_port = 80 to_port = 80 protocol = "tcp" security_groups = [aws_security_group.alb.id] } egress { description = "All outbound" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } ``` ## Resource Tagging **Consistent Tagging Strategy** ```hcl locals { # Required tags for all resources required_tags = { Environment = var.environment ManagedBy = "Terraform" Project = var.project_name CostCenter = var.cost_center Owner = var.owner_email } # Optional tags optional_tags = { Repository = "github.com/org/repo" Terraform = "true" } # Merge all tags common_tags = merge(local.required_tags, local.optional_tags, var.additional_tags) } # Use provider default tags provider "aws" { region = var.aws_region default_tags { tags = local.common_tags } } # Resource-specific tags resource "aws_instance" "app" { ami = data.aws_ami.amazon_linux_2.id instance_type = var.instance_type tags = merge(local.common_tags, { Name = "${var.name}-app" Role = "application" Backup = "daily" }) } ``` ## Cost Optimization **Cost-Aware Resource Sizing** ```hcl variable "environment" { type = string } locals { # Environment-based sizing instance_type = { production = "t3.large" staging = "t3.medium" development = "t3.micro" } rds_instance_class = { production = "db.r5.xlarge" staging = "db.t3.medium" development = "db.t3.micro" } enable_multi_az = var.environment == "production" ? true : false } resource "aws_instance" "app" { instance_type = local.instance_type[var.environment] } resource "aws_db_instance" "main" { instance_class = local.rds_instance_class[var.environment] multi_az = local.enable_multi_az } ``` **Lifecycle Management** ```hcl resource "aws_instance" "app" { ami = data.aws_ami.amazon_linux_2.id instance_type = var.instance_type lifecycle { create_before_destroy = true prevent_destroy = var.environment == "production" ignore_changes = [ami, user_data] } } # S3 lifecycle rules for cost savings resource "aws_s3_bucket_lifecycle_configuration" "data" { bucket = aws_s3_bucket.data.id rule { id = "transition-to-ia" status = "Enabled" transition { days = 30 storage_class = "STANDARD_IA" } transition { days = 90 storage_class = "GLACIER" } expiration { days = 365 } } } ``` **Resource Scheduling** ```hcl # Auto-scaling schedule for cost savings resource "aws_autoscaling_schedule" "scale_down_evening" { scheduled_action_name = "scale-down-evening" min_size = 1 max_size = 1 desired_capacity = 1 recurrence = "0 20 * * MON-FRI" autoscaling_group_name = aws_autoscaling_group.app.name } resource "aws_autoscaling_schedule" "scale_up_morning" { scheduled_action_name = "scale-up-morning" min_size = 3 max_size = 10 desired_capacity = 3 recurrence = "0 7 * * MON-FRI" autoscaling_group_name = aws_autoscaling_group.app.name } ``` ## Code Organization **Directory Structure** ``` terraform/ ├── environments/ │ ├── production/ │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── terraform.tfvars │ │ └── backend.tf │ ├── staging/ │ └── development/ ├── modules/ │ ├── vpc/ │ ├── eks/ │ └── rds/ ├── global/ │ ├── iam/ │ └── route53/ └── README.md ``` **Module Best Practices** ```hcl # Keep modules small and focused # modules/vpc/main.tf - Does ONE thing well # Clear input/output contracts # modules/vpc/variables.tf variable "cidr_block" { description = "CIDR block for VPC" type = string validation { ... } } # modules/vpc/outputs.tf output "vpc_id" { description = "ID of the VPC" value = aws_vpc.this.id } # Version all modules # modules/vpc/versions.tf terraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } ``` ## Best Practices Checklist - [ ] Use remote state with locking - [ ] Pin Terraform and provider versions - [ ] Validate all input variables - [ ] Use consistent naming conventions - [ ] Tag all resources for cost tracking - [ ] Encrypt sensitive data at rest and in transit - [ ] Implement least privilege IAM policies - [ ] Use modules for reusable components - [ ] Document module interfaces - [ ] Run terraform fmt before commit - [ ] Run terraform validate in CI/CD - [ ] Review plan output before apply - [ ] Use data sources instead of hardcoding - [ ] Implement automated testing - [ ] Use for_each instead of count - [ ] Avoid hardcoded secrets - [ ] Enable logging and monitoring - [ ] Implement cost optimization strategies - [ ] Use lifecycle rules appropriately - [ ] Keep modules focused and single-purpose