← Back to Blog
Infrastructure

Infrastructure as Code: Terraform Best Practices for 2024

January 5, 202410 min readAmol Tribhuwan

Infrastructure as Code: Terraform Best Practices for 2024

Terraform has become the de facto standard for infrastructure as code. Here are the essential best practices for writing production-grade Terraform configurations.

Project Structure

Recommended Directory Layout

terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   └── production/
├── modules/
│   ├── networking/
│   ├── compute/
│   └── database/
└── global/
    └── backend.tf

State Management

Remote Backend Configuration

terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

State Locking

Always enable state locking to prevent concurrent modifications:

resource "aws_dynamodb_table" "terraform_locks" {
  name           = "terraform-locks"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Module Design

Create Reusable Modules

# modules/vpc/main.tf
variable "environment" {
  type = string
}

variable "cidr_block" {
  type    = string
  default = "10.0.0.0/16"
}

resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

output "vpc_id" {
  value = aws_vpc.main.id
}

Use Module Sources

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "production-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"]
}

Variable Management

Use terraform.tfvars

# variables.tf
variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"

  validation {
    condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
    error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
  }
}

# terraform.tfvars
instance_type = "t3.medium"

Sensitive Variables

variable "database_password" {
  description = "RDS master password"
  type        = string
  sensitive   = true
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = false
}

Security Best Practices

Secrets Management

Never commit secrets to version control:

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
}

Resource Tagging

locals {
  common_tags = {
    Environment = var.environment
    Project     = "myproject"
    ManagedBy   = "terraform"
    CostCenter  = "engineering"
  }
}

resource "aws_instance" "web" {
  tags = merge(
    local.common_tags,
    {
      Name = "web-server"
      Role = "frontend"
    }
  )
}

Testing & Validation

Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.83.5
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_docs
      - id: terraform_tflint

Terratest for Integration Testing

func TestVPCCreation(t *testing.T) {
    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../examples/vpc",
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)
}

CI/CD Integration

GitHub Actions Workflow

name: Terraform

on:
  push:
    branches: [main]
  pull_request:

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.6.0
      
      - name: Terraform Format
        run: terraform fmt -check -recursive
      
      - name: Terraform Init
        run: terraform init
      
      - name: Terraform Validate
        run: terraform validate
      
      - name: Terraform Plan
        run: terraform plan -out=tfplan
      
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply tfplan

Performance Optimization

Use Data Sources Efficiently

# Cache AMI lookups
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}

locals {
  ami_id = data.aws_ami.ubuntu.id
}

Parallel Resource Creation

resource "aws_instance" "web" {
  count = 10
  # Terraform creates these in parallel
}

Common Pitfalls

  1. Not using terraform.lock.hcl: Always commit this file
  2. Direct resource deletion: Use terraform destroy instead
  3. Hardcoded values: Use variables and data sources
  4. No state backup: Enable versioning on state buckets
  5. Ignoring provider versions: Pin to specific versions

Advanced Patterns

Dynamic Blocks

resource "aws_security_group" "main" {
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

For_each vs Count

# Prefer for_each for better state management
resource "aws_instance" "servers" {
  for_each = toset(["web", "app", "db"])
  
  tags = {
    Name = "${each.key}-server"
  }
}

Monitoring & Alerts

Terraform Cloud Integration

terraform {
  cloud {
    organization = "mycompany"
    workspaces {
      name = "production"
    }
  }
}

Conclusion

Following these best practices will help you:

The key is consistency across your team and continuous improvement of your IaC processes.


Need help with Terraform implementation? Let's connect!

#Terraform#IaC#DevOps#Cloud