Terraform_best_practices
Chapter 38: Terraform Best Practices
Section titled “Chapter 38: Terraform Best Practices”This chapter covers best practices for structuring, organizing, and managing Terraform projects.
Project Structure
Section titled “Project Structure”┌─────────────────────────────────────────────────────────────────────────────┐│ Terraform Project Structure │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ terraform-project/ │ ││ │ │ ││ │ ├── environments/ # Environment-specific configs │ ││ │ │ ├── dev/ │ ││ │ │ │ ├── main.tf │ ││ │ │ │ ├── variables.tf │ ││ │ │ │ ├── outputs.tf │ ││ │ │ │ └── terraform.tfvars │ ││ │ │ ├── staging/ │ ││ │ │ └── prod/ │ ││ │ │ │ ││ │ ├── modules/ # Reusable modules │ ││ │ │ ├── vpc/ │ ││ │ │ ├── compute/ │ ││ │ │ └── database/ │ ││ │ │ │ ││ │ ├── .gitignore │ ││ │ ├── .terraform.lock.hcl │ ││ │ └── README.md │ ││ │ │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Alternative: Monorepo Structure
Section titled “Alternative: Monorepo Structure”terraform/├── modules/│ ├── app/│ ├── database/│ └── networking/├── services/│ ├── frontend/│ │ ├── main.tf│ │ └── variables.tf│ ├── backend/│ │ ├── main.tf│ │ └── variables.tf│ └── data/│ ├── main.tf│ └── variables.tf└── shared/ └── modules/Code Organization
Section titled “Code Organization”# main.tf - Resource definitionsresource "aws_vpc" "main" { cidr_block = var.vpc_cidr tags = local.common_tags}
resource "aws_subnet" "public" { count = length(var.availability_zones)
vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) availability_zone = var.availability_zones[count.index] map_public_ip_on_launch = true
tags = local.common_tags}variable "environment" { description = "Environment name" type = string}
variable "vpc_cidr" { description = "VPC CIDR block" type = string}
variable "availability_zones" { description = "List of AZs" type = list(string)}output "vpc_id" { value = aws_vpc.main.id}
output "public_subnet_ids" { value = aws_subnet.public[*].id}locals { common_tags = { Environment = var.environment Project = "MyProject" ManagedBy = "Terraform" Owner = "Platform Team" }}terraform { required_version = ">= 1.0.0"
required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }}Security Best Practices
Section titled “Security Best Practices”┌─────────────────────────────────────────────────────────────────────────────┐│ Terraform Security Best Practices │├─────────────────────────────────────────────────────────────────────────────┤│ ││ 1. Secrets Management ││ ✓ Use environment variables for sensitive values ││ ✓ Use -var for CLI injection ││ ✓ Use Terraform Vault provider ││ ✓ Never commit secrets to version control ││ ││ 2. State Security ││ ✓ Use remote backend with encryption ││ ✓ Enable state locking ││ ✓ Restrict access with IAM policies ││ ││ 3. Provider Security ││ ✓ Pin provider versions ││ ✓ Use official providers ││ ✓ Review provider permissions ││ ││ 4. Resource Security ││ ✓ Enable encryption on all storage ││ ✓ Use least-privilege IAM roles ││ ✓ Enable security features ││ │└─────────────────────────────────────────────────────────────────────────────┘Handling Secrets
Section titled “Handling Secrets”# BAD - Don't do this!variable "db_password" { default = "mysecretpassword" # Committed to version control!}
# GOOD - Use environment variablevariable "db_password" { description = "Database password" type = string sensitive = true}
# Usage: export TF_VAR_db_password="secret"# Or: terraform apply -var="db_password=secret"
# BETTER - Use Vaultprovider "vault" { address = "https://vault.example.com"}
data "vault_kv_secret_v2" "db_creds" { name = "database/creds"}
resource "aws_db_instance" "main" { username = data.vault_kv_secret_v2.db_creds.data.username password = data.vault_kv_secret_v2.db_creds.data.password}Provider Version Pinning
Section titled “Provider Version Pinning”terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" # Allow 5.x but not 6.0 }
random = { source = "hashicorp/random" version = "~> 3.5" } }}Naming Conventions
Section titled “Naming Conventions”┌─────────────────────────────────────────────────────────────────────────────┐│ Naming Conventions │├─────────────────────────────────────────────────────────────────────────────┤│ ││ Resources: ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ resource_type descriptive_name │ ││ │ aws_instance web_server │ ││ │ aws_s3_bucket app_assets │ ││ │ aws_security_group api_sg │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ ││ Variables: ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ snake_case │ ││ │ instance_type, vpc_cidr, allowed_cidrs │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ ││ Outputs: ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ descriptive_name (same as resource when possible) │ ││ │ instance_id, bucket_name, vpc_id │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ ││ Modules: ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ kebab-case or descriptive │ ││ │ vpc, web-server, app-container │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Consistent Naming Examples
Section titled “Consistent Naming Examples”# Resourcesresource "aws_vpc" "main" { }resource "aws_subnet" "public" { }resource "aws_subnet" "private" { }resource "aws_security_group" "web" { }resource "aws_instance" "app_server" { }resource "aws_db_instance" "main" { }resource "aws_s3_bucket" "assets" { }
# Variablesvariable "environment" { }variable "vpc_cidr_block" { }variable "instance_type" { }variable "allowed_cidr_blocks" { }
# Outputsoutput "vpc_id" { }output "public_subnet_ids" { }output "web_security_group_id" { }Testing
Section titled “Testing”# Format checkterraform fmt -check
# Validate syntaxterraform validate
# Check formatting before commitgit diff --cached | terraform fmt --check -Using tfsec for Security Scanning
Section titled “Using tfsec for Security Scanning”# Install tfsecbrew install tfsec
# Run security scantfsec .
# CI/CD integrationtfsec --format json --out tfsec-results.json .Using checkov for Compliance
Section titled “Using checkov for Compliance”# Install checkovpip install checkov
# Run checkscheckov -d .
# With Terraform planterraform plan -out tfplancheckov -f tfplanRemote State with Locking
Section titled “Remote State with Locking”terraform { backend "s3" { bucket = "company-terraform-state" key = "environments/prod/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-locks"
# Enable versioning versioning = true }}GitOps Workflow
Section titled “GitOps Workflow”┌─────────────────────────────────────────────────────────────────────────────┐│ GitOps Workflow │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ GitOps Pipeline │ ││ │ │ ││ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ ││ │ │ Commit │───▶│ Plan │───▶│ Review │───▶│ Apply │ │ ││ │ │ Code │ │ │ │ PR/MR │ │ │ │ ││ │ └────────┘ └────────┘ └────────┘ └────────┘ │ ││ │ │ ││ │ Steps: │ ││ │ 1. Developer commits changes │ ││ │ 2. CI runs terraform plan │ ││ │ 3. Plan output added to PR/MR │ ││ │ 4. Code review with plan output │ ││ │ 5. After merge, CI applies changes │ ││ │ │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘Terraform Cloud/Enterprise Features
Section titled “Terraform Cloud/Enterprise Features”# backend.tf with Terraform Cloudterraform { backend "remote" { organization = "my-org"
workspaces { name = "prod-networking" # or prefix = "networking-" } }}Sentinel Policies (Enterprise)
Section titled “Sentinel Policies (Enterprise)”import "tfplan/v2" as tfplan
# Example: Prevent creation of expensive instancesmain = rule { all tfplan.resource_changes as _, rc { rc.type is "aws_instance" implies rc.change.after.instance_type in [ "t2.micro", "t2.small", "t3.micro", "t3.small" ] }}Summary
Section titled “Summary”In this chapter, you learned:
- Project Structure: Organizing Terraform code for teams
- Code Organization: Separating concerns (variables, outputs, locals)
- Security Best Practices: Secrets, state, provider management
- Naming Conventions: Consistent naming across resources
- Testing: Format validation, security scanning
- GitOps Workflow: Automating Terraform with version control
- Remote Backend: Secure state management