What is Terraform?
Infrastructure as Code
Right now your infrastructure is defined by a series of clicks in the AWS console — there's no record of the exact configuration, and recreating it requires doing it all over again.
Infrastructure as Code (IaC) means your infrastructure is defined in files, just like application code. Those files can be:
- Version-controlled in Git (reviewed, rolled back, audited)
- Reused across environments (dev, staging, production)
- Shared with teammates
- Automated in CI/CD pipelines
Terraform is the most widely used IaC tool. It works with AWS, GCP, Azure, Cloudflare, and 1,000+ other providers through a plugin system.
How Terraform Works
.tf files → terraform plan → review diff → terraform apply → real infrastructure
↕
terraform.tfstate
- You write HCL configuration files describing the desired state
terraform plancompares your config to the current state and shows what will changeterraform applyexecutes the changes — creating, updating, or destroying resources- Terraform records what it created in a state file
HCL Syntax
Terraform uses HCL (HashiCorp Configuration Language) — human-readable and JSON-compatible.
# Configure the AWS provider
provider "aws" {
region = "us-east-1"
}
# Create an S3 bucket
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-app-uploads-12345"
}
# Create an EC2 instance
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Ubuntu 22.04 in us-east-1
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}
Core Concepts
Provider
A provider is a plugin that knows how to talk to a specific API (AWS, Cloudflare, GitHub, etc.). You declare which providers you need, and terraform init downloads them.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Resource
A resource is a piece of infrastructure — an EC2 instance, an S3 bucket, a security group. The format is resource "<type>" "<local_name>".
Variable
Variables make configs reusable:
variable "environment" {
type = string
default = "production"
}
resource "aws_instance" "web" {
instance_type = var.environment == "production" ? "t3.medium" : "t3.micro"
}
Output
Outputs expose values after apply — useful for getting resource IDs or IPs:
output "instance_ip" {
value = aws_instance.web.public_ip
}
State
Terraform stores the current state of managed resources in terraform.tfstate. This file maps your HCL config to real resource IDs in AWS. Never edit it manually.
For teams, store state remotely (S3 + DynamoDB for locking) so everyone shares the same state:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock"
}
}
The Workflow
terraform init # download providers, initialize backend
terraform plan # show what will be created/changed/destroyed
terraform apply # execute the plan (prompts for confirmation)
terraform destroy # destroy all managed resources
Always review terraform plan output before applying. It clearly shows additions (+), changes (~), and deletions (-).
A Real Example — EC2 + Security Group
resource "aws_security_group" "web" {
name = "web-sg"
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"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"] # your IP only
}
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 = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
key_name = "my-key-pair"
tags = {
Name = "web-server"
}
}
output "public_ip" {
value = aws_instance.web.public_ip
}
Run terraform apply and the EC2 instance and security group are created. Run terraform destroy to tear it all down cleanly.