Terraform: Infrastructure as Code for Beginners

Terraform: Infrastructure as Code for Beginners


Managing cloud infrastructure manually through web consoles is slow, error-prone, and impossible to reproduce. Terraform by HashiCorp solves this by letting you define your entire infrastructure in code — servers, databases, DNS records, load balancers, and more — then create and manage it all with a single command.

This guide covers Terraform fundamentals: installation, HCL syntax, providers, resources, state management, and practical examples you can run today.

What is Terraform?

Terraform is an open-source Infrastructure as Code (IaC) tool. You write configuration files that describe the infrastructure you want, and Terraform figures out how to create it. If you change the configuration, Terraform calculates what needs to change and applies only the differences.

Key benefits:

  • Declarative: You describe what you want, not how to build it
  • Provider-agnostic: Works with AWS, GCP, Azure, Cloudflare, DigitalOcean, and 3000+ other services
  • State tracking: Terraform keeps track of what it’s managing so it can update or destroy resources safely
  • Plan before apply: Always preview what changes will be made before executing them
  • Reproducible: The same configuration creates identical infrastructure every time

Official site: https://www.terraform.io

Installing Terraform

macOS (Homebrew)

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Linux (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

Windows

Download from https://developer.hashicorp.com/terraform/downloads or use Chocolatey:

choco install terraform

Verify installation:

terraform --version

HCL: HashiCorp Configuration Language

Terraform uses .tf files written in HCL. The syntax is straightforward:

# This is a comment

resource "type" "name" {
  argument1 = "value1"
  argument2 = 42
  
  nested_block {
    key = "value"
  }
}

Core Concepts

  • Provider: A plugin that connects Terraform to a cloud service (AWS, GCP, etc.)
  • Resource: A piece of infrastructure (server, database, DNS record)
  • Data Source: Read-only information fetched from a provider
  • Variable: Input parameters for your configuration
  • Output: Values displayed after applying
  • Module: A reusable group of resources

Your First Terraform Configuration

Let’s create a simple example that provisions an AWS EC2 instance.

Step 1: Create a Project Directory

mkdir terraform-demo && cd terraform-demo

Step 2: Define the Provider

Create main.tf:

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

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

Step 3: Define a Resource

Add to main.tf:

resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name        = "WebServer"
    Environment = "dev"
  }
}

Step 4: Initialize, Plan, Apply

# Download the AWS provider plugin
terraform init

# Preview what will be created
terraform plan

# Create the infrastructure
terraform apply

Terraform will show you exactly what it plans to create and ask for confirmation. Type yes to proceed.

Step 5: Inspect and Destroy

# Show current state
terraform show

# List managed resources
terraform state list

# Destroy everything
terraform destroy

Variables and Outputs

Input Variables

Create variables.tf:

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

variable "environment" {
  description = "Deployment environment"
  type        = string
  default     = "dev"
}

variable "allowed_ports" {
  description = "List of allowed ingress ports"
  type        = list(number)
  default     = [80, 443, 22]
}

Use variables in main.tf:

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

  tags = {
    Name        = "WebServer"
    Environment = var.environment
  }
}

Pass variables via:

# Command line
terraform apply -var="instance_type=t3.small"

# Variable file (terraform.tfvars)
echo 'instance_type = "t3.small"' > terraform.tfvars
terraform apply

# Environment variables
export TF_VAR_instance_type="t3.small"
terraform apply

Output Values

Create outputs.tf:

output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web_server.id
}

output "public_ip" {
  description = "Public IP address"
  value       = aws_instance.web_server.public_ip
}

After applying, outputs are displayed. You can also query them:

terraform output public_ip

State Management

Terraform tracks infrastructure in a state file (terraform.tfstate). This file maps your configuration to real resources.

For team environments, store state remotely:

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

Other backends include Azure Blob Storage, Google Cloud Storage, and Terraform Cloud.

State Commands

# List all resources in state
terraform state list

# Show details of a resource
terraform state show aws_instance.web_server

# Remove a resource from state (without destroying it)
terraform state rm aws_instance.web_server

# Move a resource in state (after renaming)
terraform state mv aws_instance.old_name aws_instance.new_name

Practical Examples

Security Group + EC2 Instance

resource "aws_security_group" "web_sg" {
  name        = "web-server-sg"
  description = "Allow HTTP and SSH"

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

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["YOUR_IP/32"]
  }

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

resource "aws_instance" "web" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web_sg.id]

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

  tags = {
    Name = "WebServer"
  }
}

Cloudflare DNS Record

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

resource "cloudflare_record" "www" {
  zone_id = var.zone_id
  name    = "www"
  value   = "203.0.113.50"
  type    = "A"
  proxied = true
}

DigitalOcean Droplet

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

provider "digitalocean" {
  token = var.do_token
}

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-22-04-x64"
  name   = "web-1"
  region = "nyc3"
  size   = "s-1vcpu-1gb"

  ssh_keys = [var.ssh_fingerprint]
}

Best Practices

1. Use a Consistent File Structure

project/
├── main.tf          # Resources
├── variables.tf     # Input variables
├── outputs.tf       # Output values
├── providers.tf     # Provider configuration
├── terraform.tfvars # Variable values (don't commit secrets)
└── .gitignore       # Ignore state files

2. .gitignore for Terraform

# .gitignore
*.tfstate
*.tfstate.*
*.tfvars
.terraform/
.terraform.lock.hcl
crash.log

3. Use Modules for Reuse

module "web_server" {
  source        = "./modules/ec2"
  instance_type = "t2.micro"
  environment   = "production"
}

4. Always Run Plan First

# Save plan to file
terraform plan -out=tfplan

# Apply the saved plan (no confirmation needed)
terraform apply tfplan

5. Use Workspaces for Environments

terraform workspace new staging
terraform workspace new production
terraform workspace select staging
terraform workspace list

Essential Terraform Commands

CommandPurpose
terraform initInitialize and download providers
terraform planPreview changes
terraform applyCreate/update infrastructure
terraform destroyDelete all managed resources
terraform fmtFormat configuration files
terraform validateCheck syntax
terraform showDisplay current state
terraform outputShow output values
terraform importImport existing infrastructure

Resources

Terraform transforms infrastructure management from a manual, error-prone process into a repeatable, version-controlled workflow. Start with a simple resource, get comfortable with the init-plan-apply cycle, and gradually build more complex configurations.