cloud architecture

Terraform Tutorial: Infrastructure as Code for Beginners (2025)

Learn Terraform from scratch. This hands-on guide teaches you Infrastructure as Code with practical AWS examples you can deploy today.

CE

CloudElevate Team

DevOps Engineers

📝cloud architecture

Terraform is the most popular Infrastructure as Code (IaC) tool, used by over 80% of organizations managing cloud infrastructure. Instead of clicking through AWS console or running CLI commands, you define your infrastructure in code files that are version-controlled, reviewable, and repeatable.

Why Infrastructure as Code?

Traditional infrastructure management has problems:

  • Manual setup is error-prone and slow
  • No record of what changed and when
  • Environment drift (dev differs from prod)
  • Disaster recovery is difficult

IaC solves all of these by treating infrastructure like application code.

Terraform Core Concepts

Providers

Providers are plugins that let Terraform interact with cloud platforms, SaaS tools, and APIs. AWS, Azure, GCP, Kubernetes, and 3000+ others are supported.

Resources

Resources are the infrastructure components you want to create: EC2 instances, S3 buckets, databases, etc.

State

Terraform tracks what infrastructure it manages in a state file. This lets it know what to create, update, or delete.

Modules

Modules are reusable packages of Terraform configuration. They help you organize code and avoid repetition.

Installing Terraform

install.sh
# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# Verify installation
terraform version

Your First Terraform Project

Let's create an AWS S3 bucket. Create a new directory and file:

setup.sh
mkdir terraform-demo && cd terraform-demo
touch main.tf

Add this to main.tf:

main.tf
# Configure the AWS Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

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

# Create an S3 bucket
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-bucket-name-12345"

  tags = {
    Name        = "My First Terraform Bucket"
    Environment = "Dev"
  }
}

# Output the bucket name
output "bucket_name" {
  value = aws_s3_bucket.my_bucket.bucket
}

The Terraform Workflow

Step 1: terraform init

Downloads provider plugins and initializes the working directory:

init.sh
terraform init

# Output:
# Initializing provider plugins...
# - Finding hashicorp/aws versions matching "~> 5.0"...
# - Installing hashicorp/aws v5.x.x...
# Terraform has been successfully initialized!

Step 2: terraform plan

Shows what Terraform will create, modify, or destroy:

plan.sh
terraform plan

# Output shows:
# + aws_s3_bucket.my_bucket will be created
#   + bucket = "my-unique-bucket-name-12345"
#   + tags   = { "Name" = "My First Terraform Bucket" }
# Plan: 1 to add, 0 to change, 0 to destroy.

Step 3: terraform apply

Creates the actual infrastructure:

apply.sh
terraform apply

# Review the plan, then type "yes" to confirm
# aws_s3_bucket.my_bucket: Creating...
# aws_s3_bucket.my_bucket: Created!
# Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Step 4: terraform destroy

Removes all resources managed by this configuration:

destroy.sh
terraform destroy
# Type "yes" to confirm deletion

Using Variables

Make your configuration flexible with variables:

variables.tf
# variables.tf
variable "environment" {
  description = "Deployment environment"
  type        = string
  default     = "dev"
}

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

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

# main.tf - using variables
provider "aws" {
  region = var.region
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type

  tags = {
    Environment = var.environment
  }
}

Pass variables via command line or .tfvars files:

tfvars.sh
# Command line
terraform apply -var="environment=prod" -var="instance_type=t3.large"

# Or create terraform.tfvars
# environment = "prod"
# instance_type = "t3.large"

Complete Example: EC2 with VPC

Here's a more complete example deploying an EC2 instance with networking:

complete-ec2.tf
# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

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

# Subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true

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

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

# Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

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

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    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                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

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

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

output "public_ip" {
  value = aws_instance.web.public_ip
}

Managing Terraform State

For team environments, store state remotely:

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

Best Practices

  • Use remote state with locking for teams
  • Never commit .tfstate files to git
  • Use variables for environment differences
  • Create modules for reusable components
  • Use terraform fmt to format code
  • Run terraform plan in CI before apply
  • Tag all resources for cost tracking

FAQ

Terraform vs CloudFormation?

Terraform works across all clouds (AWS, Azure, GCP) while CloudFormation is AWS-only. Terraform also has a larger ecosystem and more readable syntax.

What if I accidentally delete state?

This is catastrophic. Always use remote state with versioning enabled. You can import existing resources with terraform import, but it's painful.

Need Help with Infrastructure as Code?

CloudElevate has implemented Terraform for dozens of organizations. We help teams establish IaC best practices, create reusable modules, and set up CI/CD for infrastructure.

Contact us at info@cloudelevate.ai to modernize your infrastructure.

Tagged with

TerraformInfrastructure as CodeIaCAWSDevOpsCloud InfrastructureHCLAutomation

Ready to elevate your cloud infrastructure?

Get a free consultation with our DevOps experts.

View Services