Overview

Terraform lets you define cloud infrastructure in declarative configuration files, version them in Git, and apply changes consistently across environments. This tutorial walks through installing Terraform, writing your first configuration, and managing the state file.

Why Infrastructure as Code?

Manual provisioningTerraform
Click-through console changesVersioned configuration files
No record of who changed whatFull audit trail in Git
Environment driftIdentical deployments across environments
Slow to reproduceReproducible in one command

Install Terraform

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

# Ubuntu
sudo apt update && sudo apt install -y gnupg software-properties-common
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

Precompiled binaries for all platforms are available on the HashiCorp Terraform downloads page.

Verify:

terraform -version

Core Workflow

CommandPurpose
terraform initDownload provider plugins
terraform planPreview changes without applying
terraform applyCreate or modify infrastructure
terraform destroyRemove all managed resources
terraform fmtFormat configuration files
terraform validateCheck syntax and internal consistency

Step 1: Configure the Provider

Create main.tf:

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

provider "aws" {
  region = var.region
}

Step 2: Define Variables

Create variables.tf:

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

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

Step 3: Define a Resource

Create ec2.tf:

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

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

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

Step 4: Apply

terraform init
terraform plan
terraform apply

Type yes to confirm. Terraform creates the instance and prints the public IP.

Understanding State

Terraform stores the mapping between your configuration and real resources in terraform.tfstate. This file is critical. For team use, store it remotely:

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

Never commit terraform.tfstate to Git. It often contains secrets in plaintext.

Best Practices

  • Use modules to group reusable infrastructure patterns.
  • Commit a .terraform.lock.hcl file to pin provider versions.
  • Keep state in a remote backend with locking enabled.
  • Separate environments into different workspaces or directories.
  • Run terraform fmt and terraform validate in CI.