Terraform IaC AWS Infrastructure

Terraform: Complete Guide from Zero to Production Infrastructure

LM
Lucas Martinez
Infrastructure Lead
Jul 15, 2025
30 min read

What You'll Learn

A complete Terraform guide from zero to production — providers, resources, variables, state management, modules, workspaces, and deploying a full AWS infrastructure stack.

What is Terraform?

Terraform is an open-source Infrastructure as Code (IaC) tool by HashiCorp that lets you define cloud and on-premises infrastructure in human-readable configuration files — then provision it consistently and repeatably.

Declarative

Describe what you want, not how to get there

Idempotent

Run the same config multiple times — same result

Multi-Cloud

Same language for AWS, Azure, GCP, and 1000+ providers

The Terraform Workflow

bash — Core Terraform Commands
# 1. INIT — Download providers and initialize backend
terraform init

# 2. PLAN — Preview changes without applying
terraform plan
terraform plan -out=tfplan         # Save plan to file
terraform plan -var="env=staging"  # Pass variable

# 3. APPLY — Create/update infrastructure
terraform apply
terraform apply tfplan             # Apply saved plan (no prompt)
terraform apply -auto-approve      # Skip confirmation (CI/CD use only!)
terraform apply -target=aws_instance.web  # Apply specific resource only

# 4. DESTROY — Tear down infrastructure
terraform destroy
terraform destroy -target=aws_instance.web

# Other useful commands
terraform validate                 # Validate configuration syntax
terraform fmt                      # Format .tf files
terraform state list               # List all resources in state
terraform state show aws_vpc.main  # Show specific resource state
terraform import aws_s3_bucket.backup my-bucket  # Import existing resource
terraform output                   # Show output values
terraform graph | dot -Tsvg > graph.svg  # Visualize dependencies

Complete AWS Infrastructure Example

main.tf — VPC + EC2 + RDS on AWS
# ─────────────── Provider Configuration ───────────────
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.30"
    }
  }

  # Remote State Backend (recommended for teams)
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"  # State locking
  }
}

provider "aws" {
  region = var.aws_region
  default_tags {
    tags = {
      Project     = "MyApp"
      Environment = var.environment
      ManagedBy   = "Terraform"
    }
  }
}

# ─────────────── VPC ───────────────
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = { Name = "${var.environment}-vpc" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "${var.environment}-igw" }
}

# Public subnets across 2 AZs
resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
  tags = { Name = "${var.environment}-public-${count.index + 1}" }
}

# Private subnets for database
resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 10}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = { Name = "${var.environment}-private-${count.index + 1}" }
}

# ─────────────── Security Groups ───────────────
resource "aws_security_group" "web" {
  name   = "${var.environment}-web-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# ─────────────── EC2 Instance ───────────────
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type

  subnet_id              = aws_subnet.public[0].id
  vpc_security_group_ids = [aws_security_group.web.id]
  iam_instance_profile   = aws_iam_instance_profile.web.name
  key_name               = var.key_pair_name

  user_data = base64encode(templatefile("${path.module}/scripts/userdata.sh.tpl", {
    app_name = var.app_name
    env      = var.environment
  }))

  root_block_device {
    volume_type = "gp3"
    volume_size = 20
    encrypted   = true
  }

  lifecycle {
    create_before_destroy = true
  }

  tags = { Name = "${var.environment}-web-server" }
}

# ─────────────── RDS Database ───────────────
resource "aws_db_instance" "main" {
  identifier           = "${var.environment}-database"
  engine               = "postgres"
  engine_version       = "16.1"
  instance_class       = "db.t3.micro"
  allocated_storage    = 20
  max_allocated_storage = 100  # Auto-scaling storage
  storage_encrypted    = true

  db_name  = "myapp"
  username = var.db_username
  password = random_password.db_password.result

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

  backup_retention_period = 7
  deletion_protection     = true
  skip_final_snapshot     = false
  final_snapshot_identifier = "${var.environment}-final-snapshot"

  tags = { Name = "${var.environment}-db" }
}

Variables and Outputs

variables.tf + outputs.tf
### variables.tf ###
variable "aws_region" {
  description = "AWS region to deploy into"
  type        = string
  default     = "us-east-1"
}

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

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

variable "db_username" {
  description = "Database master username"
  type        = string
  sensitive   = true  # Will be masked in plan output
}

### terraform.tfvars ###
aws_region    = "us-east-1"
environment   = "prod"
instance_type = "t3.medium"
db_username   = "dbadmin"

### outputs.tf ###
output "web_public_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

output "database_endpoint" {
  description = "RDS connection endpoint"
  value       = aws_db_instance.main.endpoint
  sensitive   = true  # Won't print in console output
}

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

Terraform Modules — Reusable Infrastructure

Using modules
# Using public registry modules
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "${var.environment}-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 = true  # Use one NAT GW (cost savings in dev)
}

# Using a local module
module "web_server" {
  source = "./modules/ec2-webserver"

  environment    = var.environment
  vpc_id         = module.vpc.vpc_id
  subnet_id      = module.vpc.public_subnets[0]
  instance_type  = var.instance_type
}

State Management — Critical!

  • Never commit state files to Git — They contain sensitive data (passwords, keys).
  • Always use remote state — S3 + DynamoDB for AWS. Enables state locking.
  • Use workspaces for environmentsterraform workspace new staging creates isolated state.
  • Be careful with terraform destroy — There's no undo. Use deletion_protection = true on databases.
  • Import existing resources — Don't delete and recreate; use terraform import to bring them under management.

Keep Reading

D
DevOps

Docker Networking Demystified: Bridge, Host & Overlay

8 min read Read More
C
Cloud

AWS IAM Roles vs Users vs Policies

10 min read Read More
P
Programming

Understanding Python's GIL & Multiprocessing

14 min read Read More