Skip to content

Infrastructure as Code


IaC Overview

IaC Benefits


Terraform

Basic Structure

# main.tf

terraform {
  required_version = ">= 1.5.0"

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

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

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "Terraform"
      Project     = var.project_name
    }
  }
}

Variables

# variables.tf

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment must be dev, staging, or production."
  }
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.medium"
}

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "List of availability zones"
  type        = list(string)
  default     = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

variable "tags" {
  description = "Additional tags"
  type        = map(string)
  default     = {}
}

# terraform.tfvars
environment = "production"
instance_type = "t3.large"

Resources

# VPC and Networking
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

resource "aws_subnet" "private" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 4, count.index)
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project_name}-private-${count.index + 1}"
  }
}

# ECS Cluster
resource "aws_ecs_cluster" "main" {
  name = "${var.project_name}-cluster"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

# ECS Service
resource "aws_ecs_service" "app" {
  name            = "${var.project_name}-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = var.desired_count
  launch_type     = "FARGATE"

  network_configuration {
    subnets         = aws_subnet.private[*].id
    security_groups = [aws_security_group.ecs.id]
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.app.arn
    container_name   = "app"
    container_port   = 8080
  }

  lifecycle {
    ignore_changes = [desired_count]  # Allow autoscaling
  }
}

Modules

# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.cidr_block
  # ...
}

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

# main.tf (usage)
module "vpc" {
  source = "./modules/vpc"

  cidr_block = "10.0.0.0/16"
  environment = var.environment
}

module "vpc_prod" {
  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", "us-east-1c"]
  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_nat_gateway = true
  single_nat_gateway = false
}

Data Sources

# Look up existing resources
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

data "aws_caller_identity" "current" {}

data "aws_region" "current" {}

# Use in resources
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type
}

Outputs

# outputs.tf

output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "load_balancer_dns" {
  description = "DNS name of the load balancer"
  value       = aws_lb.main.dns_name
}

output "database_endpoint" {
  description = "RDS endpoint"
  value       = aws_db_instance.main.endpoint
  sensitive   = true
}

Terraform Commands

# Initialize
terraform init
terraform init -upgrade  # Upgrade providers

# Plan
terraform plan
terraform plan -out=tfplan  # Save plan
terraform plan -target=aws_instance.web  # Specific resource

# Apply
terraform apply
terraform apply tfplan  # Apply saved plan
terraform apply -auto-approve  # Skip confirmation

# Destroy
terraform destroy
terraform destroy -target=aws_instance.web

# State management
terraform state list
terraform state show aws_instance.web
terraform state mv aws_instance.web aws_instance.new_name
terraform state rm aws_instance.web
terraform import aws_instance.web i-1234567890

# Workspace (for multiple environments)
terraform workspace list
terraform workspace new staging
terraform workspace select production

# Format and validate
terraform fmt
terraform fmt -recursive
terraform validate

# Output
terraform output
terraform output -json

State Management

Terraform State

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

# Reference remote state
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "network/terraform.tfstate"
    region = "us-east-1"
  }
}

# Use outputs from remote state
resource "aws_instance" "web" {
  subnet_id = data.terraform_remote_state.network.outputs.subnet_id
}

Best Practices

Terraform Best Practices


Comparison: Terraform vs CloudFormation vs Pulumi

IaC Tools Comparison


Pulumi Example

# Python Pulumi example
import pulumi
import pulumi_aws as aws

# Create VPC
vpc = aws.ec2.Vpc("main-vpc",
    cidr_block="10.0.0.0/16",
    enable_dns_hostnames=True,
    tags={"Name": "main-vpc"}
)

# Create subnet
subnet = aws.ec2.Subnet("main-subnet",
    vpc_id=vpc.id,
    cidr_block="10.0.1.0/24",
    availability_zone="us-east-1a",
    tags={"Name": "main-subnet"}
)

# Create ECS cluster
cluster = aws.ecs.Cluster("app-cluster",
    settings=[aws.ecs.ClusterSettingArgs(
        name="containerInsights",
        value="enabled"
    )]
)

# Export outputs
pulumi.export("vpc_id", vpc.id)
pulumi.export("cluster_arn", cluster.arn)

Common Interview Questions

  1. What is Terraform state?
  2. Maps real infrastructure to config
  3. Tracks resource metadata
  4. Should be stored remotely for teams

  5. How to manage multiple environments?

  6. Workspaces
  7. Separate directories
  8. Variable files per environment

  9. Terraform vs CloudFormation?

  10. Terraform: Multi-cloud, HCL, community modules
  11. CF: AWS-native, managed state, deep AWS integration

  12. What is drift?

  13. When real infrastructure differs from code
  14. Detected with terraform plan
  15. Reconciled with terraform apply

  16. How to handle secrets?

  17. Don't store in code
  18. Use AWS Secrets Manager, Vault
  19. Mark outputs as sensitive

  • *