Infrastructure as Code with Terraform: Best Practices
Master Terraform with production-ready best practices, real-world patterns, and proven methodologies to automate cloud infrastructure safely and efficiently.
🎯 Benefits in Numbers
⏱️ Reading time: 14 min | 💡 Level: Intermediate to Expert
📋 Why This Guide?
Challenge: Organizations struggle with infrastructure consistency across multiple cloud environments, spending excessive time on manual configurations, and face critical risks from configuration drift and human error. Infrastructure as Code (IaC) with Terraform solves these issues by making infrastructure predictable, version-controlled, and repeatable.
Measured Impact
Deployment Speed & Reliability
🗓️ 5-Step Terraform Mastery Framework
Calyo Infrastructure Automation Framework™
Foundation & Setup
Install Terraform, configure providers, setup remote state (S3/Azure Storage), establish naming conventions
Module Architecture
Design module structure, create networking, compute, database modules, test modules locally
Production Implementation
Deploy to dev/staging/prod, integrate with Git, setup terraform apply workflows, implement cost controls
Governance & Optimization
Implement Sentinel/OPA policies, enable cost optimization, security scanning, continuous compliance
Advanced Patterns
Multi-cloud strategies, complex state management, dynamic resource generation
Foundation & Setup
Install Terraform, configure providers, setup remote state (S3/Azure Storage), establish naming conventions
Module Architecture
Design module structure, create networking, compute, database modules, test modules locally
Production Implementation
Deploy to dev/staging/prod, integrate with Git, setup terraform apply workflows, implement cost controls
Governance & Optimization
Implement Sentinel/OPA policies, enable cost optimization, security scanning, continuous compliance
Advanced Patterns
Multi-cloud strategies, complex state management, dynamic resource generation
📝 Step 1: Foundation & Terraform Setup
🎯 Measurable Objectives
⚠️ Critical Setup Pitfalls vs Solutions
Common Setup Mistakes & Workarounds
Classic Trap | Risk Level | Calyo Solution |
|---|---|---|
| Local state files only | Critical | Always use remote state (S3, Azure Storage, Terraform Cloud) with state locking enabled |
| Hardcoded credentials in code | Critical | Use IAM roles, environment variables, or Terraform variables with sensitive flag |
| No .tfstate backup strategy | High | Enable versioning on state bucket, implement daily backups, use state snapshots |
| Inconsistent naming conventions | Medium | Define naming standard from day 1: resource_type-environment-purpose format |
| Missing variable validation | Medium | Use variable type constraints, add validation blocks, document all inputs |
| No state isolation per environment | High | Separate state files for dev/staging/prod using workspaces or separate backends |
✅ Foundation Checklist Completion
Setup Phase Completion (%)
Core Setup Template:
# main.tf - Provider Configuration
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "terraform-state-prod"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
CostCenter = var.cost_center
}
}
}💡 Calyo Tip: Always enable state encryption and DynamoDB locking from day one. These take 5 minutes to configure but prevent catastrophic data loss and concurrent modification conflicts.
📝 Step 2: Module Architecture & Reusability
🛠️ Recommended Module Stack
Essential Terraform Modules by Use Case
Module Type | Typical Scope | Reusability Score | Complexity |
|---|---|---|---|
| Networking (VPC) | VPC, subnets, security groups, routing | 95% | High |
| Compute (EC2) | Instances, auto-scaling, load balancing | 88% | Medium |
| Database | RDS, DynamoDB, ElastiCache instances | 92% | Medium |
| Kubernetes (EKS) | Cluster, node groups, add-ons | 85% | High |
| Security & IAM | Roles, policies, KMS keys | 90% | Medium |
| Logging & Monitoring | CloudWatch, S3 logging, alarms | 87% | Medium |
| Storage (S3) | Buckets, versioning, lifecycle rules | 91% | Low |
| CDN & DNS | CloudFront, Route53 records | 89% | Low |
📊 Best Practice Module Structure
Code Organization Score by Component (/100)
Module Directory Structure:
terraform/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── locals.tf
│ │ └── terraform.tf
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── ec2.tf
│ ├── database/
│ │ └── ...
│ └── security/
│ └── ...
├── environments/
│ ├── dev/
│ │ └── main.tf
│ ├── staging/
│ │ └── main.tf
│ └── production/
│ └── main.tf
├── global/
│ └── terraform.tf
└── _docs/
└── MODULE_GUIDELINES.mdComplete Module Example - Networking:
# modules/networking/main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.project}-${var.environment}-vpc"
cidr = var.vpc_cidr
azs = data.aws_availability_zones.available.names
private_subnets = var.private_subnets
public_subnets = var.public_subnets
enable_nat_gateway = true
single_nat_gateway = var.environment != "production"
enable_vpn_gateway = var.enable_vpn
enable_dns_hostnames = true
tags = {
Name = "${var.project}-vpc"
}
}
resource "aws_security_group" "alb" {
name = "${var.project}-${var.environment}-alb-sg"
description = "Security group for ALB"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project}-alb-sg"
}
}💡 Calyo Tip: Always use the Terraform Registry modules as your foundation (terraform-aws-modules, terraform-google-modules). They’re battle-tested and regularly updated, reducing 80% of your module development time.
📝 Step 3: Multi-Environment Implementation
Environment Strategy Comparison
Which environment strategy to choose?
| Critère | Workspaces Single backend, multiple states | Separate Backends Isolated state per environment | Hybrid Approach Workspaces for dev, separate backends for prod |
|---|---|---|---|
| State Isolation | |||
| Team Safety | |||
| Operational Complexity | |||
| Disaster Recovery | |||
| Cost Visibility |
Production-Ready Environment Configuration:
# environments/production/main.tf
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/TerraformRole"
}
}
module "networking" {
source = "../../modules/networking"
project = var.project
environment = "production"
vpc_cidr = "10.0.0.0/16"
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_vpn = true
}
module "eks" {
source = "../../modules/compute"
project = var.project
environment = "production"
cluster_name = "${var.project}-eks-prod"
kubernetes_version = "1.28"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
node_groups = {
primary = {
desired_size = 3
min_size = 3
max_size = 10
instance_types = ["t3.large"]
disk_size = 50
}
}
}
module "database" {
source = "../../modules/database"
project = var.project
environment = "production"
database_name = "${replace(var.project, "-", "_")}_prod"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.r6i.xlarge"
allocated_storage = 100
max_allocated_storage = 500
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
backup_retention_days = 30
enable_multiaz = true
enable_encryption = true
}📝 Step 4: Governance & Policy as Code
🎯 Governance Objectives
⚠️ Governance Pitfalls vs Solutions
Common Governance Mistakes & Enforcement
Governance Gap | Risk Level | Best Practice Solution |
|---|---|---|
| No cost controls | High | Implement cost limits via Sentinel, use budget alerts, enforce instance rightsizing |
| Missing encryption enforcement | Critical | Policy: All S3 buckets must have encryption. All RDS must have EBS encryption enabled |
| Untagged resources | Medium | Mandatory tags on all resources. Tag enforcement via Sentinel or OPA policies |
| Open security groups | Critical | Restrict SSH/RDP to specific IPs. Deny 0.0.0.0/0 for dangerous ports |
| No audit logging | Critical | Enable CloudTrail, S3 access logs, VPC Flow Logs for all infrastructure |
| Unmanaged database backups | High | Enforce backup retention (min 14 days), enable automated backups |
Sentinel Policy Example - Cost Control:
# policies/cost_control.sentinel
import "tfplan/v2" as tfplan
# Allowed instance types by environment
allowed_types = {
"development": ["t3.micro", "t3.small", "t3.medium"],
"staging": ["t3.medium", "t3.large", "m6i.large"],
"production": ["m6i.large", "m6i.xlarge", "c6i.large"]
}
# Deny expensive instance types in non-prod
deny_expensive_instances = rule {
all tfplan.resource_changes as address, rc {
rc.type == "aws_instance" {
rc.change.after.instance_type in allowed_types[rc.change.after.environment]
}
}
}
main = rule {
deny_expensive_instances
}📝 Step 5: Advanced Patterns & Optimization
🛠️ Advanced Terraform Features
Advanced Features by Maturity Level
Feature | Use Case | Complexity | Impact |
|---|---|---|---|
| Dynamic Blocks | Multi-item configurations | Medium | Reduces 40% code duplication |
| For_each Loops | Multiple resource instances | Medium | Enables parametric scaling |
| Computed Values | Complex configurations | High | Allows intelligent defaults |
| Resource Targeting | Partial deployments | Low | Crisis recovery tool |
| Import Existing Resources | State migration | High | 50% faster legacy adoption |
| Custom Providers | Special integrations | Expert | Unlimited extensibility |
Advanced Pattern: Dynamic Web Server Deployment
# Advanced example with dynamic blocks
variable "web_servers" {
type = map(object({
instance_type = string
volume_size = number
environment = string
}))
}
# Dynamic security group rules
resource "aws_security_group" "web" {
name = "${var.project}-web-sg"
dynamic "ingress" {
for_each = var.allowed_ports
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
# Multiple instances using for_each
resource "aws_instance" "servers" {
for_each = var.web_servers
instance_type = each.value.instance_type
root_block_device {
volume_size = each.value.volume_size
encrypted = true
}
tags = {
Name = each.key
Environment = each.value.environment
}
}Optimizing State Performance:
# Use local_file for large data to avoid state bloat
resource "local_file" "large_config" {
content = jsonencode(var.complex_config)
filename = "${path.module}/configs/generated.json"
}
# Reference in outputs, not state
output "config_path" {
value = local_file.large_config.filename
}
# Use data sources to avoid state storage
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}💡 Calyo Tip: Keep your state files lean. Use data sources for lookups, local_file for large configs, and separate state for independent components. A 500MB state file will kill your Terraform performance.
📊 Implementation Success Metrics
Essential KPIs
- Infrastructure Deployment Time: Measure from code commit to production. Target: < 15 minutes
- Configuration Drift: Monthly percentage of manual changes outside Terraform. Target: < 2%
- Team Adoption Rate: Percentage of infrastructure deployed via Terraform. Target: > 95%
- Plan Success Rate: Plans that apply without errors. Target: > 98%
- Cost Forecast Accuracy: Budget vs actual. Target: ±5%
- State Lock Contention: Instances of simultaneous apply attempts. Target: 0
Critical Monitoring Dashboard Elements
- Real-time plan execution logs
- State file size and change frequency trends
- Cost estimates vs actuals by resource type
- Drift detection scan results (daily)
- Policy violation trends by type
- Team deploy frequency and success rates
💡 Expert Tips & Quick Wins
Week 1 Quick Wins
- Set up remote state with locking - Takes 30 minutes, prevents 90% of state conflicts
- Create first 3 reusable modules - VPC, EC2, Security Group - reduces future setup by 70%
- Establish naming conventions - Prevents resource chaos as infrastructure scales
- Configure provider defaults - Default tags on all resources for billing and compliance
Long-term Investments
- Build internal module registry - Centralizes best practices, accelerates team onboarding
- Implement policy framework - Sentinel/OPA policies enforce compliance without manual reviews
- Multi-cloud strategy - Design modules for AWS, Azure, GCP compatibility
- Advanced state management - Separate state backends per environment, implement automated backups
- Custom providers development - Integrate with proprietary systems and legacy infrastructure
- Team training program - Invest in Terraform certification and advanced training
🚀 Going Further
Complementary Resources
- 📥 Terraform Best Practices Checklist: 147-item pre-production validation guide
- 📊 Module Template Library: Production-ready modules for AWS, Azure, GCP
- 🎓 Terraform Mastery Masterclass: 8-week intensive with real-world scenarios
- 📚 State Management Deep Dive: Advanced state strategies for enterprise scale
Advanced Use Cases
- Multi-account AWS deployments - Terraform with AWS Organizations and Service Control Policies
- Kubernetes at scale - EKS provisioning with auto-scaling and GitOps integration (ArgoCD/Flux)
- Disaster recovery automation - Cross-region failover orchestration with Terraform
- Compliance as Code - Automated compliance checking for HIPAA, PCI-DSS, SOC 2 standards
- FinOps automation - Automated resource rightsizing and cost optimization enforcement
❓ Frequently Asked Questions
Q: How do we migrate existing infrastructure to Terraform? A: Use terraform import for existing resources, organize by module, then plan and apply incrementally. Start with non-critical infrastructure (dev/staging) to validate your module structure before touching production.
Q: What’s the best way to manage secrets in Terraform? A: Never store secrets in code. Use AWS Secrets Manager, Azure Key Vault, or Terraform Cloud’s sensitive variables. Reference them via data sources (aws_secretsmanager_secret_version) rather than hardcoding.
Q: How do we handle team collaboration safely? A: Always use remote state with state locking (DynamoDB + S3 or Terraform Cloud). Require code reviews for production changes, use Terraform Cloud enforcement for plan reviews, and separate dev/staging/prod state files.
Q: Should we use workspaces or separate backends? A: For production safety, separate backends per environment are recommended. Workspaces are convenient for dev but lack the isolation needed for production environments with different access controls.
Q: How often should we refactor our Terraform code? A: Plan quarterly refactoring reviews. As infrastructure grows from 50 to 500+ resources, refactor into more granular modules. Use terraform state mv to reorganize without destroying resources.
Q: What’s the typical learning curve for teams new to Terraform? A: Basic proficiency (simple modules, multi-environment setup): 3-4 weeks. Intermediate (advanced features, governance): 8-12 weeks. Expert level (custom providers, large-scale optimization): 6+ months.
📊 Terraform Maturity Roadmap
Month 1-2: Foundation
├─ Remote state with locking
├─ Basic module structure
└─ First environment deployment
Month 3-4: Scaling
├─ Multi-environment setup
├─ Team collaboration workflows
└─ 6-8 production modules
Month 5-6: Governance
├─ Policy as Code (Sentinel/OPA)
├─ Cost controls & monitoring
└─ Compliance automation
Month 7+: Advanced
├─ Multi-cloud strategies
├─ Internal module registry
└─ Advanced state patterns & self-service platforms- terraform
- infrastructure-as-code
- cloud-automation
- devops
- best-practices
- aws
- azure


