Module 7 of 13 · DevOps & Platform Engineering · Intermediate

Infrastructure as Code with Terraform

Duration: 140 min

Terraform is an Infrastructure as Code (IaC) tool that enables defining, versioning, and managing infrastructure declaratively. This module covers Terraform fundamentals, AWS providers, modules, state management, and best practices.

Terraform Fundamentals

Terraform uses HCL (HashiCorp Configuration Language) to define infrastructure:

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

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

# Define a resource
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-bucket-name"

  tags = {
    Name        = "My Bucket"
    Environment = "production"
  }
}

# Output values
output "bucket_name" {
  value       = aws_s3_bucket.my_bucket.id
  description = "The name of the S3 bucket"
}

Terraform Workflow

# Initialize Terraform (download providers, setup backend)
terraform init

# Validate configuration syntax
terraform validate

# Format code to standard style
terraform fmt -recursive

# Plan changes (preview what will be created/modified)
terraform plan -out=tfplan

# Apply changes (create/modify resources)
terraform apply tfplan

# Destroy resources
terraform destroy

# Show current state
terraform show

# List resources in state
terraform state list

# Show specific resource details
terraform state show aws_s3_bucket.my_bucket

Variables and Outputs

# variables.tf
variable "environment" {
  type        = string
  description = "Environment name"
  default     = "development"
}

variable "instance_count" {
  type        = number
  description = "Number of instances"
  default     = 1
}

variable "tags" {
  type        = map(string)
  description = "Common tags for all resources"
  default = {
    Project = "MyApp"
    Team    = "DevOps"
  }
}

# main.tf
resource "aws_instance" "app" {
  count           = var.instance_count
  ami             = "ami-0c55b159cbfafe1f0"
  instance_type   = "t3.micro"
  
  tags = merge(
    var.tags,
    {
      Name = "app-${count.index + 1}"
    }
  )
}

# outputs.tf
output "instance_ids" {
  value       = aws_instance.app[*].id
  description = "IDs of created instances"
}

output "instance_ips" {
  value       = aws_instance.app[*].private_ip
  description = "Private IPs of instances"
}

Terraform Modules

Modules organize and reuse infrastructure code:

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true

  tags = {
    Name = var.name
  }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidr
  availability_zone = var.availability_zone

  tags = {
    Name = "${var.name}-public"
  }
}

# modules/vpc/variables.tf
variable "name" {
  type = string
}

variable "cidr_block" {
  type = string
}

variable "public_subnet_cidr" {
  type = string
}

variable "availability_zone" {
  type = string
}

# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

output "subnet_id" {
  value = aws_subnet.public.id
}

# main.tf - Using the module
module "vpc" {
  source = "./modules/vpc"

  name                 = "production"
  cidr_block           = "10.0.0.0/16"
  public_subnet_cidr   = "10.0.1.0/24"
  availability_zone    = "us-east-1a"
}

State Management

Terraform state tracks resource metadata. Store state remotely for team collaboration:

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

Setup remote state:

# Create S3 bucket for state
aws s3api create-bucket \
  --bucket my-terraform-state \
  --region us-east-1

# Enable versioning
aws s3api put-bucket-versioning \
  --bucket my-terraform-state \
  --versioning-configuration Status=Enabled

# Enable encryption
aws s3api put-bucket-encryption \
  --bucket my-terraform-state \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      }
    }]
  }'

# Create DynamoDB table for state locking
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

Workspaces

Workspaces manage multiple environments with same configuration:

# Create workspace
terraform workspace new production

# List workspaces
terraform workspace list

# Switch workspace
terraform workspace select production

# Use workspace in configuration
resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = terraform.workspace == "production" ? "t3.large" : "t3.micro"

  tags = {
    Environment = terraform.workspace
  }
}

AWS Resources with Terraform

EC2 Instance

resource "aws_instance" "web" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  user_data = base64encode(<<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl start nginx
  EOF
  )

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

RDS Database

resource "aws_db_instance" "main" {
  identifier     = "mydb"
  engine         = "postgres"
  engine_version = "15.3"
  instance_class = "db.t3.micro"

  allocated_storage = 20
  storage_type      = "gp3"

  db_name  = "myapp"
  username = "admin"
  password = random_password.db_password.result

  skip_final_snapshot = false
  final_snapshot_identifier = "mydb-final-snapshot"

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

resource "random_password" "db_password" {
  length  = 16
  special = true
}

Load Balancer

resource "aws_lb" "main" {
  name               = "my-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = [aws_subnet.public1.id, aws_subnet.public2.id]

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

resource "aws_lb_target_group" "app" {
  name        = "app-tg"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "instance"

  health_check {
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 3
    interval            = 30
    path                = "/"
    matcher             = "200"
  }
}

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
  }
}

Terraform Best Practices

Code Organization

terraform/
├── main.tf              # Main configuration
├── variables.tf         # Variable definitions
├── outputs.tf           # Output definitions
├── backend.tf           # Backend configuration
├── terraform.tfvars     # Variable values (gitignored)
├── modules/
│   ├── vpc/
│   ├── rds/
│   └── ecs/
└── environments/
    ├── dev/
    ├── staging/
    └── production/

Validation and Testing

# Validate syntax
terraform validate

# Format code
terraform fmt -recursive

# Use tflint for linting
tflint --init
tflint

# Use terraform-docs to generate documentation
terraform-docs markdown . > README.md

❓ What is the primary purpose of Terraform?

❓ What does `terraform plan` do?

❓ What is a Terraform module?

❓ Where should Terraform state be stored for team collaboration?

❓ What is the purpose of Terraform workspaces?

← Previous Continue interactively → Next →

Related Courses