Terraform by HashiCorp is the industry standard for Infrastructure as Code (IaC). It lets you define, provision, and manage cloud resources across AWS, GCP, Azure, and 2000+ providers using declarative configuration files. This guide takes you from zero to production-ready Terraform.
What is Infrastructure as Code?
IaC is the practice of managing infrastructure (servers, networks, databases) through machine-readable definition files, rather than manual processes or ad-hoc scripts. Benefits include:
- Version control — Your entire infrastructure is in Git
- Repeatability — Spin up identical environments every time
- Self-documentation — The code IS the documentation
- Automation — Integrate with CI/CD pipelines
- Drift detection — Terraform detects manual changes
Installing Terraform
# Linux installation
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 Configuration
Create a file named main.tf to provision an AWS EC2 instance:
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0c7217cdde317cfec"
instance_type = "t3.micro"
tags = {
Name = "TerraformDemo"
}
}
Running Terraform
# Initialize — downloads providers
terraform init
# Format code
terraform fmt
# See execution plan
terraform plan
# Apply changes
terraform apply
# Destroy resources
terraform destroy
Core Concepts Deep Dive
State Management
Terraform tracks your infrastructure state in a state file (terraform.tfstate). For teams, always use remote state storage:
# backend.tf — Store state in S3 with DynamoDB locking
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
Variables & Outputs
# variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "environment" {
description = "Deployment environment"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Must be dev, staging, or prod."
}
}
# outputs.tf
output "instance_ip" {
description = "Public IP of the web server"
value = aws_instance.web.public_ip
}
# terraform.tfvars — override defaults
instance_type = "t3.medium"
environment = "staging"
Modules (Reusable Infrastructure)
Modules are the key to DRY infrastructure. Here's how to structure them:
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
# modules/vpc/variables.tf
variable "environment" { type = string }
variable "vpc_cidr" { type = string }
# environments/prod/main.tf — using the module
module "vpc" {
source = "../../modules/vpc"
environment = "prod"
vpc_cidr = "10.0.0.0/16"
}
Production-Grade Directory Structure
infrastructure/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── ecs/
│ │ └── ...
│ └── rds/
│ └── ...
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── backend.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ └── ...
│ └── prod/
│ ├── main.tf
│ ├── backend.tf
│ └── terraform.tfvars
└── global/
└── iam/
└── main.tf
CI/CD Integration
Automate Terraform with GitHub Actions:
# .github/workflows/terraform.yml
name: Terraform CI/CD
on:
push:
branches: [main]
paths: [infrastructure/**]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: ./environments/prod
- name: Terraform Plan
run: terraform plan -no-color
working-directory: ./environments/prod
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
working-directory: ./environments/prod
terraform plan in CI before terraform apply to catch errors early. Use terraform Plan output as a PR comment for visibility.
Best Practices Checklist
- ✅ Use remote state with locking (S3 + DynamoDB)
- ✅ Isolate environments with workspaces or directories
- ✅ Pin provider versions
- ✅ Use modules for reusable components
- ✅ Validate variables with
validationblocks - ✅ Run
terraform fmtin CI to enforce style - ✅ Use
prevent_destroyon critical resources - ✅ Tag all resources with environment, project, owner
- ✅ Enable versioning on state bucket
- ✅ Use
terraform_remote_statesparingly
Ready to automate more? Check out our Python Automation Scripts and CI/CD Pipeline Guide.