Skip to main content
graphwiz.ai
← Back to Posts

Terraform on AWS: Building Production Infrastructure from Scratch

DevOpsInfrastructure
terraformawsinfrastructure-as-codecloud

Terraform on AWS: Building Production Infrastructure from Scratch

Infrastructure as Code (IaC) transforms how we provision and manage cloud resources. Terraform has become the de facto standard for multi-cloud infrastructure automation.

Why Terraform?

  • Declarative - Describe desired state, not steps to achieve it
  • Provider-agnostic - Same syntax for AWS, GCP, Azure, and more
  • State management - Track what exists and what changed
  • Plan before apply - Preview changes before execution
  • Modular - Reusable components across projects

Project Setup

Directory Structure

Terraform Project Structure

Provider Configuration

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

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

provider "aws" {
  region = var.aws_region
}

VPC Module

module "vpc" {
  source = "./modules/vpc"

  name               = "production"
  cidr               = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]

  public_subnets     = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnets    = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = false  # Use true for dev, false for prod

  tags = {
    Environment = "production"
    Terraform   = "true"
  }
}

EC2 Instances

module "ec2" {
  source = "./modules/ec2"

  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t3.medium"
  key_name               = var.key_name
  vpc_security_group_ids = [module.vpc.default_security_group_id]
  subnet_ids             = module.vpc.private_subnet_ids

  min_size = 2
  max_size = 6
  desired  = 3

  user_data = templatefile("${path.module}/user_data.sh", {
    db_host = module.rds.endpoint
  })
}

RDS Database

module "rds" {
  source = "./modules/rds"

  identifier     = "production-db"
  engine         = "postgres"
  engine_version = "15.4"

  instance_class = "db.t3.medium"
  allocated_storage = 100

  db_name  = "appdb"
  username = var.db_username
  password = var.db_password

  multi_az               = true
  db_subnet_group_name   = module.vpc.database_subnet_group
  vpc_security_group_ids = [module.vpc.database_security_group_id]

  backup_retention_period = 7
  skip_final_snapshot     = false
}

Best Practices

State Management

  1. Use remote state - Never commit .tfstate files
  2. Enable locking - Prevent concurrent modifications
  3. Encrypt state - Sensitive data may be stored
  4. Back up state - Version your state file

Security

# Never hardcode secrets
variable "db_password" {
  type      = string
  sensitive = true  # Marks as sensitive in logs
}

# Use AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "prod/database/password"
}

Modularization

  • Create reusable modules for common patterns
  • Version modules with Git tags
  • Use the Terraform Registry for community modules

Common Commands

# Initialize
terraform init

# Plan changes
terraform plan -out=tfplan

# Apply changes
terraform apply tfplan

# Destroy resources
terraform destroy

Conclusion

Terraform provides a robust foundation for infrastructure automation. Start simple, add complexity incrementally, and always use remote state with locking.