Table of Contents
- What is Terraform?
- Installation
- Core Concepts
- Basic Examples
- State Management
- Variables and Outputs
- Modules
- Common Commands
- Best Practices
What is Terraform?
Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp. It allows you to define and provision infrastructure using a declarative configuration language called HCL (HashiCorp Configuration Language).
Key Benefits (with details and limitations):
-
Declarative: Describe what you want, not how to get it
- Better than imperative (using python/java/go sdk's). Terraform figures out how to achieve the state, simply declare, I want se bucket with versioning enabled. Complexity of calling the API is hidden.
-
Multi-cloud: Works with AWS, Azure, GCP, and many other providers
- Terraform's architecture is built on a lightweight core binary that leverages a powerful plugin-based system. Providers—which interface with cloud platforms and services—are downloaded automatically during initialization (
terraform init), enabling support for major public clouds and hundreds of other infrastructure providers without requiring a large installation footprint.
- Terraform's architecture is built on a lightweight core binary that leverages a powerful plugin-based system. Providers—which interface with cloud platforms and services—are downloaded automatically during initialization (
-
Version Control: Infrastructure code can be versioned like application code
- Terraform configuration files serve as the single source of truth for infrastructure. Since all changes are made through code, manual configuration drift is eliminated, and we can track every infrastructure change through version control systems like Git, enabling full audit trails and rollback capabilities.
-
Idempotent: Running the same configuration multiple times produces the same result
- While idempotency is a key advantage, some cloud providers impose limitations that can affect this behavior. For example, both AWS and Google Cloud Platform (GCP) have mandatory deletion waiting periods for KMS keys. AWS KMS keys enter a scheduled deletion state and cannot be fully removed for 7-30 days (configurable), while GCP Cloud KMS key versions remain in a "scheduled for destruction" state for typically 30 days. This means that attempting to destroy and immediately recreate a KMS key with the same name will fail, as the key still exists in a pending deletion state. Such provider-specific constraints can prevent true idempotency in certain scenarios, requiring careful resource naming strategies or waiting periods between destroy and recreate operations.
-
State Management: Tracks the current state of your infrastructure
- The state file is critical for Terraform to understand and manage your infrastructure. However, migrating state files during major Terraform version upgrades or backend changes can be complex and may require manual intervention. While Terraform provides migration tools (like
terraform init -migrate-state), careful planning and testing are essential to ensure state compatibility and avoid accidental infrastructure destruction during the migration process.
- The state file is critical for Terraform to understand and manage your infrastructure. However, migrating state files during major Terraform version upgrades or backend changes can be complex and may require manual intervention. While Terraform provides migration tools (like
Installation
macOS
brew install terraform
Linux
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
Verify Installation
terraform version
Core Concepts
1. Providers
Providers are plugins that Terraform uses to interact with cloud platforms, SaaS providers, and other APIs.
2. Resources
Resources are the most important element in Terraform. They represent infrastructure objects like virtual machines, networks, databases, etc.
3. Data Sources
Data sources allow you to fetch and use information from existing infrastructure or external systems. Unlike resources, data sources are read-only and don't create, modify, or destroy anything. They're useful for referencing existing resources, getting the latest AMI IDs, querying VPC information, or retrieving data from external APIs.
4. Variables
Variables allow you to parameterize your configurations.
5. Outputs
Outputs expose information about your infrastructure after it's created.
6. State
Terraform stores the state of your infrastructure in a state file, which tracks what resources exist and their current configuration.
Basic Examples
Prerequisites
Before running the examples below, ensure you have:
- Terraform binary - Install Terraform and specify the version in your configuration
- Providers - Declare required providers with version constraints in your Terraform configuration
- Cloud credentials - Configure authentication for cloud providers (AWS, GCP, Azure, etc.)
Example 1: Local File Resource (doesn't require credentials)
Create a file called main.tf:
terraform {
required_version = ">= 1.0"
}
# Create a local file
resource "local_file" "example" {
filename = "example.txt"
content = "Hello, Terraform!"
}
Commands:
terraform init # Initialize Terraform
terraform plan # Preview changes
terraform apply # Apply changes
terraform destroy # Remove resources
Example 2: AWS S3 Bucket
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
# Create an S3 bucket
resource "aws_s3_bucket" "example" {
bucket = "my-terraform-example-bucket-2024"
tags = {
Name = "Example Bucket"
Environment = "Development"
}
}
# Enable versioning
resource "aws_s3_bucket_versioning" "example" {
bucket = aws_s3_bucket.example.id
versioning_configuration {
status = "Enabled"
}
}
Setup:
# Configure AWS credentials
aws configure
# Initialize and apply
terraform init
terraform plan
terraform apply
Example 3: Google Cloud Storage (GCS) Bucket
terraform {
required_version = ">= 1.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
provider "google" {
project = "your-project-id" # Replace with your GCP project ID
region = "us-central1" # specify region
}
# Create a GCS bucket
resource "google_storage_bucket" "example" {
name = "my-terraform-example-bucket-2025" # Must be globally unique
location = "US"
force_destroy = false # Set to true to allow deletion of non-empty bucket
# Enable versioning
versioning {
enabled = true
}
# Lifecycle rules
lifecycle_rule {
condition {
age = 30
}
action {
type = "Delete"
}
}
# Uniform bucket-level access
uniform_bucket_level_access = true
# Labels
labels = {
environment = "development"
managed-by = "terraform"
}
}
# Set bucket IAM policy (optional)
resource "google_storage_bucket_iam_member" "public_read" {
bucket = google_storage_bucket.example.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
# Output the bucket URL
output "bucket_url" {
value = google_storage_bucket.example.url
description = "URL of the GCS bucket"
}
output "bucket_name" {
value = google_storage_bucket.example.name
description = "Name of the GCS bucket"
}
Setup:
# Authenticate with Google Cloud
gcloud auth application-default login
# Set your project
gcloud config set project your-project-id
# Initialize and apply
terraform init
terraform plan
terraform apply
State Management
Local State (Default)
By default, Terraform stores state locally in a file called terraform.tfstate.
Remote State (Recommended for Teams)
Store state in a remote backend to enable team collaboration and state locking. Examples for AWS S3 and Google Cloud Storage (GCS):
AWS S3 Backend:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "terraform.tfstate"
region = "us-east-1"
# Enable state locking with DynamoDB
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
Google Cloud Storage (GCS) Backend:
terraform {
backend "gcs" {
bucket = "my-terraform-state-bucket"
prefix = "terraform/state"
# Optional: Enable state locking (requires Cloud Storage bucket with versioning)
# State locking is automatically enabled with GCS backend
}
}
State Commands
terraform state list # List all resources in state
terraform state show <resource> # Show details of a resource
terraform state mv <old> <new> # Move a resource in state
terraform state rm <resource> # Remove a resource from state
Variables and Outputs
Variables Example
Create variables.tf:
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "tags" {
description = "Common tags for all resources"
type = map(string)
default = {
Project = "Terraform-101"
ManagedBy = "Terraform"
}
}
Create terraform.tfvars:
region = "us-west-2"
instance_type = "t3.small"
environment = "dev"
tags = {
Project = "Terraform-101"
ManagedBy = "Terraform"
Owner = "DevOps Team"
}
Update main.tf to use variables:
provider "aws" {
region = var.region
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
tags = merge(
var.tags,
{
Name = "example-instance-${var.environment}"
Environment = var.environment
}
)
}
Outputs Example
Create outputs.tf:
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.example.id
}
output "instance_public_ip" {
description = "Public IP address of the EC2 instance"
value = aws_instance.example.public_ip
}
output "instance_arn" {
description = "ARN of the EC2 instance"
value = aws_instance.example.arn
sensitive = false
}
Modules
Modules are reusable Terraform configurations. They help organize and encapsulate infrastructure.
Using a Module
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.0.0.0/16"
environment = "production"
tags = {
Project = "Terraform-101"
}
}
# Reference module outputs
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_id
# ...
}
Creating a Module
Create modules/vpc/main.tf:
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
}
variable "environment" {
description = "Environment name"
type = string
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = merge(
var.tags,
{
Name = "vpc-${var.environment}"
Environment = var.environment
}
)
}
output "vpc_id" {
value = aws_vpc.main.id
}
output "vpc_cidr" {
value = aws_vpc.main.cidr_block
}
Using Public Modules
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
tags = {
Terraform = "true"
Environment = "dev"
}
}
Common Commands
Basic Workflow
# Initialize Terraform
terraform init
# Format code
terraform fmt
# Validate configuration
terraform validate
# Plan changes
terraform plan
# Apply changes
terraform apply
# Apply with auto-approve (use with caution)
terraform apply -auto-approve
# Destroy infrastructure
terraform destroy
# Show current state
terraform show
Best Practices
1. File Organization
project/
├── main.tf # Main resources
├── variables.tf # Variable declarations
├── outputs.tf # Output definitions
├── terraform.tfvars # Variable values
├── providers.tf # Provider configuration
└── modules/ # Reusable modules
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf
2. Use Version Constraints
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Allows 5.x but not 6.0
}
}
}
3. Use Variables, Not Hardcoded Values
# ❌ Bad
resource "aws_instance" "example" {
instance_type = "t2.micro"
}
# ✅ Good
resource "aws_instance" "example" {
instance_type = var.instance_type
}
4. Use Meaningful Resource Names
# ❌ Bad
resource "aws_instance" "a" { }
# ✅ Good
resource "aws_instance" "web_server" { }
5. Tag Your Resources
resource "aws_instance" "example" {
tags = {
Name = "web-server"
Environment = var.environment
Project = "Terraform-101"
ManagedBy = "Terraform"
}
}
6. Use Remote State
Store state in a remote backend (S3, Azure Storage, GCS) for team collaboration.
7. Use Workspaces for Environments
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
8. Validate and Format Before Committing
terraform fmt -recursive
terraform validate
9. Use Data Sources
# Get the latest AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "example" {
ami = data.aws_ami.amazon_linux.id
# ...
}
10. Use terraform.tfvars for Sensitive Data
Never commit terraform.tfvars with secrets. Use environment variables or secret management:
export TF_VAR_db_password="secret-password"
Common Patterns
Conditional Resources
resource "aws_instance" "example" {
count = var.create_instance ? 1 : 0
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
}
For Each Loop
variable "regions" {
type = map(object({
cidr = string
}))
default = {
us-east-1 = { cidr = "10.0.0.0/16" }
us-west-2 = { cidr = "10.1.0.0/16" }
}
}
resource "aws_vpc" "example" {
for_each = var.regions
cidr_block = each.value.cidr
tags = {
Name = "vpc-${each.key}"
}
}
Locals for Computed Values
locals {
common_tags = {
Project = "Terraform-101"
Environment = var.environment
ManagedBy = "Terraform"
}
instance_name = "${var.project_name}-${var.environment}-${var.instance_type}"
}
resource "aws_instance" "example" {
tags = local.common_tags
# ...
}
Troubleshooting
Common Issues
- State Lock Error
terraform force-unlock <LOCK_ID>
- Provider Not Found
terraform init -upgrade
- State Out of Sync
terraform refresh
terraform plan
- Import Existing Resources
terraform import aws_s3_bucket.example my-bucket-name
Next Steps
- Practice: Start with local resources, then move to cloud providers
- Learn Modules: Create reusable modules for common patterns
- State Management: Set up remote state backends
- CI/CD Integration: Integrate Terraform into your CI/CD pipeline
- Advanced Topics: Learn about Terraform Cloud, opa policies, and workspaces
Resources
Happy Terraforming! 🚀
Top comments (0)