Cloud & Devops

Infrastructure Automation with Terraform: Complete Practical Guide for System Admins

Introduction: Infrastructure Automation Changes Everything

Managing infrastructure manually is a big problem.

Infrastructure automation with Terraform solves it.

What used to take 6 hours now takes 6 minutes.

What used to require 5 people now one person does.

This guide teaches infrastructure automation with Terraform practically:

  • Real setup steps
  • Real AWS/Azure examples
  • Real problem-solving
  • Real best practices

Not theory. Just hands-on infrastructure automation.


What Is Terraform? (Simplified)

Terraform is infrastructure as code tool.

Instead of clicking buttons in AWS console:

You write code.
Terraform reads code.
Terraform creates infrastructure.
Infrastructure is consistent, repeatable, version-controlled.

Why infrastructure automation matters:

Manual clicks:
- Inconsistent (different each time)
- Error-prone (easy to miss something)
- Slow (takes hours)
- Not repeatable (hard to do again)
- No history (who changed what?)

Terraform code:
- Consistent (identical every time)
- Reliable (code doesn't forget)
- Fast (minutes)
- Repeatable (run again anytime)
- Version control (see all changes)

Part 1: Installing and Setting Up Terraform

Step 1: Install Terraform

On Windows:

1. Download Terraform from https://www.terraform.io/downloads.html
2. Unzip file to C:\Program Files\Terraform
3. Add to PATH environment variable
4. Open PowerShell
5. Type: terraform version
6. Should show version (e.g., Terraform v1.5.0)

On Mac:

brew install terraform
terraform version

On Linux:

wget https://releases.hashicorp.com/terraform/1.5.0/terraform_1.5.0_linux_amd64.zip
unzip terraform_1.5.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
terraform version

Time: 10 minutes

Reference: Official Terraform Download


Step 2: Configure Cloud Provider Access

For AWS:

1. Create AWS account if needed
2. Create IAM user with admin access
3. Get Access Key ID and Secret Access Key
4. Install AWS CLI: https://aws.amazon.com/cli/
5. Configure credentials:
   aws configure
   Enter Access Key ID
   Enter Secret Access Key
   Select region (e.g., us-east-1)

For Azure:

1. Create Azure account
2. Install Azure CLI: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli
3. Login:
   az login
4. Choose subscription

Time: 15-20 minutes

Reference: AWS IAM Setup


Part 2: First Infrastructure Automation Project

Step 3: Create Your First Terraform Configuration

Create file: main.tf

Simple AWS example (create one EC2 instance):

hcl
# Configure the AWS provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# Create a security group
resource "aws_security_group" "web" {
  name        = "web-security-group"
  description = "Allow HTTP and SSH"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # Allow from anywhere
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # Allow from anywhere
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]  # Allow all outbound
  }
}

# Create an EC2 instance
resource "aws_instance" "web" {
  ami                    = "ami-0c55b159cbfafe1f0"  # Amazon Linux 2
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web.id]

  user_data = <<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>Hello from Terraform</h1>" > /var/www/html/index.html
              EOF

  tags = {
    Name = "web-server"
  }
}

# Output the instance IP
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of web server"
}

What this does:

  • Creates security group (firewall rules)
  • Creates EC2 instance (server)
  • Installs web server automatically
  • Outputs the IP address

Time: 10 minutes to write


Step 4: Deploy Infrastructure (Infrastructure Automation in Action)

powershell
# Initialize Terraform (downloads AWS provider)
terraform init

# Preview what will be created
terraform plan

# Create the infrastructure
terraform apply
# Type "yes" when prompted

# Wait 2-3 minutes for instance to launch
# Terraform will output the IP address

Output:

instance_ip = "54.123.45.67"

Time: 5 minutes

Visit: http://54.123.45.67 in browser → See “Hello from Terraform”


Step 5: Modify Infrastructure (Change Code, Apply Changes)

Edit main.tf:

Change instance type:

hcl
instance_type = "t2.small"  # Was t2.micro

Apply changes:

powershell
terraform plan    # See what will change
terraform apply   # Apply the change

Terraform automatically:

  • Stops micro instance
  • Starts small instance
  • Maintains everything else

No downtime. No manual work.


Part 3: Real-World Infrastructure Automation Examples

Example 1: VPC with Multiple Subnets and EC2 Instances

Use case: Create complete network infrastructure

hcl
# Create VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "main-vpc"
  }
}

# Create public subnet
resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "public-subnet"
  }
}

# Create private subnet
resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1b"

  tags = {
    Name = "private-subnet"
  }
}

# Create Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "main-igw"
  }
}

# Create route table for public subnet
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block      = "0.0.0.0/0"
    gateway_id      = aws_internet_gateway.main.id
  }

  tags = {
    Name = "public-rt"
  }
}

# Associate route table with subnet
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# Create 3 EC2 instances in public subnet
resource "aws_instance" "web" {
  count              = 3
  ami                = "ami-0c55b159cbfafe1f0"
  instance_type      = "t2.micro"
  subnet_id          = aws_subnet.public.id

  tags = {
    Name = "web-server-${count.index + 1}"
  }
}

# Output IPs
output "instance_ips" {
  value       = [for instance in aws_instance.web : instance.public_ip]
  description = "Public IPs of all instances"
}

What this infrastructure automation creates:

  • VPC (isolated network)
  • 2 subnets (public and private)
  • Internet Gateway (for internet access)
  • Route table (for routing)
  • 3 EC2 instances (in public subnet)

Execute:

powershell
terraform init
terraform apply
# Creates entire network in minutes

Example 2: RDS Database with Infrastructure as Code

Use case: Create production database automatically

hcl
# Create RDS security group
resource "aws_security_group" "rds" {
  name = "rds-security-group"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
  }
}

# Create DB subnet group
resource "aws_db_subnet_group" "main" {
  name       = "db-subnet-group"
  subnet_ids = [aws_subnet.private.id, aws_subnet.public.id]

  tags = {
    Name = "db-subnet-group"
  }
}

# Create RDS instance
resource "aws_db_instance" "main" {
  identifier     = "production-database"
  engine         = "postgres"
  engine_version = "15.3"
  instance_class = "db.t3.micro"

  allocated_storage = 20
  storage_type      = "gp2"

  db_name  = "myappdb"
  username = "admin"
  password = "MySecurePassword123!"  # Better: use AWS Secrets Manager

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]

  backup_retention_period = 30
  multi_az                = true

  tags = {
    Name = "production-db"
  }
}

# Output database endpoint
output "database_endpoint" {
  value       = aws_db_instance.main.endpoint
  description = "RDS endpoint"
}

What this creates:

  • RDS PostgreSQL database
  • Configured for production
  • Automatic backups (30 days)
  • Multi-AZ (high availability)

Security note: Don’t hardcode passwords. Use AWS Secrets Manager.

Reference: AWS Secrets Manager with Terraform


Example 3: Load Balancer with Auto-Scaling

Use case: Infrastructure automation for scalable applications**

hcl
# Create load balancer
resource "aws_lb" "main" {
  name               = "app-load-balancer"
  internal           = false
  load_balancer_type = "application"
  subnets            = [aws_subnet.public.id, aws_subnet.public.id]

  tags = {
    Name = "app-lb"
  }
}

# Create target group
resource "aws_lb_target_group" "app" {
  name        = "app-target-group"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
}

# Create listener
resource "aws_lb_listener" "app" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# Create launch template
resource "aws_launch_template" "app" {
  name_prefix   = "app-"
  image_id      = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

# Create auto-scaling group
resource "aws_autoscaling_group" "app" {
  name                = "app-asg"
  vpc_zone_identifier = [aws_subnet.public.id]
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"

  min_size         = 2
  max_size         = 10
  desired_capacity = 3

  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
}

# Output load balancer DNS
output "load_balancer_dns" {
  value = aws_lb.main.dns_name
}

What this creates:

  • Load balancer (distributes traffic)
  • Auto-scaling group (automatically adds/removes servers)
  • Automatically scales from 2 to 10 instances
  • High availability automatically

Part 4: Terraform State Management (Critical)

Understanding Terraform State

Terraform maintains terraform.tfstate file.

This file stores:

  • What infrastructure exists
  • Configuration of each resource
  • Metadata

Important: This file is sensitive. Protect it.

State Management Best Practices

Local state (for learning only):

tfstate file on your computer
Works for single person
NOT for teams

Remote state (for production):

AWS S3 backend:

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

Azure Storage backend:

hcl
terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform"
    storage_account_name = "mystorageaccount"
    container_name       = "tfstate"
    key                  = "prod.tfstate"
  }
}

Benefits:

  • Team can share state
  • State is backed up
  • State is version controlled
  • Infrastructure is consistent across team

Reference: Terraform State Management


Part 5: Terraform Modules (Reusable Code)

What Are Modules?

Module = Reusable Terraform code.

Instead of writing VPC code every time, create a module.

Module structure:

modules/
├── vpc/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── rds/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── ec2/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf

Using Modules

hcl
module "vpc" {
  source = "./modules/vpc"
  
  cidr_block = "10.0.0.0/16"
  name       = "production-vpc"
}

module "rds" {
  source = "./modules/rds"
  
  vpc_id           = module.vpc.vpc_id
  db_name          = "production_db"
  db_username      = "admin"
  db_password      = "SecurePassword123"
}

module "ec2" {
  source = "./modules/ec2"
  
  vpc_id = module.vpc.vpc_id
  count  = 3
}

Benefits:

  • Write once, use everywhere
  • Consistent infrastructure
  • Easy to maintain
  • Easy to scale

Part 6: Best Practices for Infrastructure Automation

1. Use Variables for Flexibility

Bad (hardcoded):

hcl
instance_type = "t2.micro"

Good (variable):

hcl
variable "instance_type" {
  type    = string
  default = "t2.micro"
}

resource "aws_instance" "web" {
  instance_type = var.instance_type
}

Use it:

powershell
terraform apply -var="instance_type=t2.small"

2. Use Environment Files

Create production.tfvars:

hcl
instance_type = "t2.large"
desired_capacity = 10
environment = "production"

Create staging.tfvars:

hcl
instance_type = "t2.micro"
desired_capacity = 2
environment = "staging"

Deploy:

powershell
terraform apply -var-file="production.tfvars"
terraform apply -var-file="staging.tfvars"

3. Use Outputs for Important Information

hcl
output "database_endpoint" {
  value       = aws_db_instance.main.endpoint
  description = "Database connection string"
}

output "load_balancer_dns" {
  value       = aws_lb.main.dns_name
  description = "Load balancer DNS"
}

output "instance_ips" {
  value       = [for instance in aws_instance.web : instance.public_ip]
  description = "All instance IPs"
}

4. Organize Code Logically

terraform/
├── main.tf              (main resources)
├── variables.tf         (variable definitions)
├── outputs.tf           (outputs)
├── terraform.tfvars     (variable values)
├── dev.tfvars           (dev-specific values)
├── prod.tfvars          (prod-specific values)
└── modules/
    ├── vpc/
    ├── rds/
    └── ec2/

5. Use .gitignore

Never commit sensitive files:

# .gitignore
terraform.tfstate
terraform.tfstate.backup
*.tfvars
.terraform/

Part 7: Common Terraform Patterns

Pattern 1: Count (Create Multiple Resources)

hcl
resource "aws_instance" "web" {
  count         = 5  # Create 5 instances
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = "web-${count.index}"  # Names: web-0, web-1, etc
  }
}

# Access specific instance
output "first_instance_ip" {
  value = aws_instance.web[0].public_ip
}

Pattern 2: For_each (Create from Map)

hcl
variable "environments" {
  default = {
    dev  = "t2.micro"
    staging = "t2.small"
    prod = "t2.large"
  }
}

resource "aws_instance" "app" {
  for_each = var.environments

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = each.value

  tags = {
    Name = "app-${each.key}"
  }
}

Pattern 3: Locals (Reusable Values)

hcl
locals {
  environment = "production"
  project     = "myapp"
  
  common_tags = {
    Environment = local.environment
    Project     = local.project
    ManagedBy   = "Terraform"
  }
}

resource "aws_instance" "web" {
  tags = local.common_tags
}

Part 8: Troubleshooting Common Issues

Issue 1: State Lock

Problem: “Error acquiring the state lock”

Cause: Another terraform apply is running

Solution:

powershell
# Force unlock (dangerous!)
terraform force-unlock LOCK_ID

# Better: Wait for other apply to finish

Issue 2: Resource Already Exists

Problem: “Error creating resource: already exists”

Cause: Resource created outside Terraform

Solution:

powershell
# Import existing resource
terraform import aws_instance.web i-1234567890abcdef0

# Now Terraform knows about it

Issue 3: Syntax Errors

Problem: “Invalid or unsupported combinations of arguments”

Solution:

powershell
# Validate syntax
terraform validate

# Format code properly
terraform fmt

Part 9: Real-World Infrastructure Automation Workflow

Complete Production Setup

powershell
# 1. Initialize
terraform init

# 2. Plan and review
terraform plan -var-file="production.tfvars" > plan.txt
# Review plan.txt before applying

# 3. Apply
terraform apply -var-file="production.tfvars" -auto-approve

# 4. Verify
terraform output
# Check all outputs are correct

# 5. Monitor
# Monitor AWS console for resource health
# Monitor application logs

# 6. When done
# To destroy: terraform destroy -var-file="production.tfvars"

Conclusion: Infrastructure Automation Is Standard Now

Infrastructure automation with Terraform isn’t optional.

It’s standard practice.

Start today:

  1. Install Terraform
  2. Create simple infrastructure
  3. Expand to complex infrastructure
  4. Use modules for reusability
  5. Share with team

You’ll never click AWS console again.

Read Also :

AWS for Infrastructure Engineers: Complete Beginner’s Guide (2026)

Mo Assem

My name is Mohamed Assem, and I am a Cloud & Infrastructure Engineer with over 14 years of experience in IT, working across both Microsoft Azure and AWS. My expertise lies in cloud operations, automation, and building modern, scalable infrastructure. I design and implement CI/CD pipelines and infrastructure as code solutions using tools like Terraform and Docker to streamline operations and improve efficiency. Through my blog, TechWithAssem, I share practical tutorials, real-world implementations, and step-by-step guides to help engineers grow in Cloud and DevOps.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button