What is Terraform?
Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp. It allows you to define, provision, and manage infrastructure across multiple cloud providers and services using a declarative configuration language.
Unlike imperative tools that require you to specify the exact steps to create infrastructure, Terraform uses a declarative approach. You describe the desired end state, and Terraform determines the actions needed to achieve it.
“Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files.”
— HashiCorp
How Terraform Works
Terraform operates through a clear, three-step workflow that manages infrastructure lifecycle from creation to destruction.
Write
Define infrastructure in HCL configuration files. Describe resources, providers, and their relationships.
Plan
Preview changes Terraform will make. The plan shows what will be created, modified, or destroyed.
Apply
Execute the plan to create or modify infrastructure. Terraform calls provider APIs to make changes.
State
Track the current state of infrastructure. State enables Terraform to detect drift and plan updates.
Installing Terraform
# macOS (with Homebrew)# Ubuntu/Debian# Ubuntu/Debian# Ubuntu/Debian# Ubuntu/Debian# Ubuntu/Debian# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Windows (with Chocolatey)# Verify installation# Verify installationallation
terraform --versionHCL Basics
HashiCorp Configuration Language (HCL) is the language used to write Terraform configurations. It's designed to be human-readable and machine-friendly.
Basic Syntax
# Comments start with hash// Or double slash# Blocks define configuration sections# Arguments use key = value syntax# Arguments use key = value syntax# Arguments use key = value syntax# Arguments use key = value syntax# Arguments use key = value syntax# Arguments use key = value syntax# Arguments use key = value syntax# Expressions reference other values# Strings support interpolation# Strings support interpolation# Strings support interpolation# Multiline strings use heredoc# Multiline strings use heredocuse heredoc
script = <<-EOF
#!/bin/bash
echo "Hello World"
EOFData Types
# Strings# Numbers# Numbers# Numbers# Booleans# Booleans# Booleans# Lists# Lists# Lists# Lists# Maps# Maps# Maps# Maps# Maps# Maps# Maps# Maps# Maps# Maps# Maps# Maps# Objects# Objects# Objects# Objects# Objects# Objects# Objects# Objects# Objectsts
instance_config = {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
monitoring = true
}Providers and Resources
Providers are plugins that enable Terraform to interact with cloud platforms, SaaS providers, and other APIs. Resources are the infrastructure objects managed by providers.
Provider Configuration
# versions.tf - Provider version constraints# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Provider configuration# Multiple provider configurations with aliases# Multiple provider configurations with aliases# Multiple provider configurations with aliases# Multiple provider configurations with aliasesconfigurations with aliases
provider "aws" {
alias = "us_east"
region = "us-east-1"
}Resource Definitions
# Basic resource# Resource with dependencies# Resource with dependencies# Resource with dependencies# Resource with dependencies# Resource with dependencies# Resource with dependencies# Resource with dependencies# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with count# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_each# Resource with for_eacheach
resource "aws_s3_bucket" "buckets" {
for_each = toset(["logs", "assets", "backups"])
bucket = "my-company-${each.value}-${random_id.suffix.hex}"
}Variables and Outputs
Input Variables
# variables.tf
variable "environment" {
description = "The deployment environment"
type = string
default = "development"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Environment must be development, staging, or production."
}
}
variable "instance_count" {
description = "Number of instances to create"
type = number
default = 1
}
variable "enable_monitoring" {
description = "Enable detailed monitoring"
type = bool
default = false
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
default = ["eu-west-2a", "eu-west-2b"]
}
variable "instance_config" {
description = "Instance configuration"
type = object({
ami = string
instance_type = string
key_name = optional(string)
})
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}Output Values
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "instance_public_ips" {
description = "Public IPs of EC2 instances"
value = aws_instance.web[*].public_ip
}
output "database_endpoint" {
description = "Database connection endpoint"
value = aws_db_instance.main.endpoint
sensitive = true # Marks output as sensitive
}Setting Variables
# terraform.tfvars# Command line# Command line# Command line# Command line# Command line# Command line# Environment variables# Environment variables# Environment variables# Variable file# Variable file# Variable file# Variable fileile
terraform apply -var-file="production.tfvars"State Management
Terraform state is a critical concept. The state file tracks which real-world resources correspond to your configuration, enabling Terraform to determine what changes need to be made.
Remote Backend (S3)
# backend.tf# Create the S3 bucket and DynamoDB table first# Create the S3 bucket and DynamoDB table first# Create the S3 bucket and DynamoDB table first# Create the S3 bucket and DynamoDB table first# Create the S3 bucket and DynamoDB table first# Create the S3 bucket and DynamoDB table firstket and DynamoDB table first
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}State Commands
# List resources in state# Show resource details# Show resource details# Move resource in state# Move resource in state# Remove resource from state (without destroying)# Remove resource from state (without destroying)# Import existing infrastructure# Import existing infrastructure# Import existing infrastructure# Pull remote state locally# Pull remote state locally# Pull remote state locally locally
terraform state pull > backup.tfstateWorkspaces
Workspaces allow you to manage multiple instances of your infrastructure (e.g., development, staging, production) using the same configuration.
# Create and switch workspaces# List workspaces# List workspaces# List workspaces# List workspaces# List workspaces# List workspaces# List workspaces# Switch workspace# Switch workspace# Show current workspace# Show current workspace# Show current workspace# Using workspace in configuration# Conditional count based on workspace# Conditional count based on workspace# Conditional count based on workspace# Conditional count based on workspace# Conditional count based on workspace# Conditional count based on workspace# Conditional count based on workspacee
resource "aws_instance" "worker" {
count = terraform.workspace == "production" ? 5 : 1
# ...
}Data Sources
Data sources allow Terraform to query and use information from external sources or existing infrastructure not managed by Terraform.
# Get latest Amazon Linux 2 AMI# Get current AWS region# Get current AWS region# Get current AWS region# Get current AWS region# Get current AWS region# Get current AWS region# Get current AWS region# Get current caller identity# Get current caller identity# Get availability zones# Get availability zones# Get availability zones# Using data sources# Using data sources# Using data sources# Using data sources# Using data sources sources
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
availability_zone = data.aws_availability_zones.available.names[0]
}Built-in Functions
Terraform includes many built-in functions for transforming and manipulating data.
String Functions
join(separator, list)Joins list elements with separator
split(separator, string)Splits string into list
lower(string)Converts to lowercase
upper(string)Converts to uppercase
replace(string, search, replace)Replaces occurrences
trimspace(string)Removes leading/trailing whitespace
Collection Functions
length(list)Returns number of elements
merge(map1, map2)Merges maps together
concat(list1, list2)Concatenates lists
flatten(list)Flattens nested lists
keys(map)Returns map keys as list
values(map)Returns map values as list
Numeric Functions
min(numbers...)Returns smallest number
max(numbers...)Returns largest number
abs(number)Returns absolute value
ceil(number)Rounds up to nearest integer
floor(number)Rounds down to nearest integer
Filesystem Functions
file(path)Reads file contents as string
fileexists(path)Checks if file exists
templatefile(path, vars)Renders template with variables
basename(path)Returns filename from path
dirname(path)Returns directory from path
Terraform Workflow
| Command | Description |
|---|---|
| terraform init | Initialises the working directory and downloads providers |
| terraform plan | Creates an execution plan showing what will change |
| terraform apply | Applies changes to reach the desired state |
| terraform destroy | Destroys all managed infrastructure |
| terraform fmt | Formats configuration files to canonical style |
| terraform validate | Validates the configuration files |
| terraform output | Displays output values from state |
| terraform state list | Lists resources in the state file |
| terraform import | Imports existing infrastructure into state |
| terraform workspace | Manages workspaces for environment separation |
Typical Workflow
# 1. Initialise the working directory# 2. Format configuration files# 3. Validate configuration# 3. Validate configuration# 4. Create execution plan# 4. Create execution plan# 5. Apply the plan# 5. Apply the plan# 6. View outputs# 6. View outputs# 6. View outputs# 7. When done, destroy infrastructureestroy infrastructure
terraform destroyBest Practices
Use Remote State
Store state in a remote backend like S3 or Terraform Cloud for team collaboration and state locking.
Use Variables
Parameterise your configurations to make them reusable across environments.
Format Consistently
Run terraform fmt regularly to maintain consistent code style across your team.
Use Modules
Encapsulate reusable infrastructure patterns into modules for consistency and maintainability.
Review Plans Carefully
Always review terraform plan output before applying to avoid unintended changes.
Version Pin Providers
Specify provider version constraints to ensure reproducible builds.
Recommended File Structure
terraform-project/
├── main.tf # Primary resource definitions
├── variables.tf # Input variable declarations
├── outputs.tf # Output value declarations
├── versions.tf # Provider and Terraform versions
├── backend.tf # Backend configuration
├── data.tf # Data source definitions
├── locals.tf # Local values
├── terraform.tfvars # Variable values (not in git)
├── modules/ # Local modules
│ └── vpc/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── environments/ # Environment-specific configs
├── dev.tfvars
├── staging.tfvars
└── production.tfvarsTroubleshooting
Common issues and solutions when working with Terraform.
State lock errors
Error: “Error acquiring the state lock”
Common causes:
- Previous Terraform run crashed or was interrupted
- Another team member is running Terraform
- Stale lock in DynamoDB or storage backend
Solution:
# Check who holds the lock# For S3 backend, check DynamoDB table for stale locks# Always use -lock-timeout for long operations# Always use -lock-timeout for long operationsse -lock-timeout for long operations
terraform apply -lock-timeout=10mProvider authentication failures
Error: “Error configuring provider: no valid credential sources found”
Common causes:
- Missing or expired credentials
- Wrong region or profile specified
- IAM role without proper trust relationship
Solution:
# AWS: Check credentials# Set credentials explicitly# Set credentials explicitly# Or use profiles# Or use profiles# Or use profiles# Or use profiles# Or use profiles# Or use profiles# Azure: Login and set subscription# Azure: Login and set subscription# GCP: Authenticate# GCP: Authenticate# GCP: Authenticate# GCP: Authenticate: Authenticate
gcloud auth application-default loginState drift and unexpected changes
Symptoms: Plan shows changes you didn't make, or resources keep getting recreated
Common causes:
- Manual changes made outside Terraform
- Different Terraform or provider versions
- Default values changed in provider
- Ignored attributes causing perpetual diff
Solution:
# Refresh state from actual infrastructure# Import manually created resources# Use lifecycle to ignore specific changes# Use lifecycle to ignore specific changese lifecycle to ignore specific changes
resource "aws_instance" "example" {
# ...
lifecycle {
ignore_changes = [tags["LastModified"]]
}
}Dependency cycle errors
Error: “Cycle: resource.a, resource.b”
Common causes:
- Two resources referencing each other
- Module outputs creating circular dependencies
- Security group rules referencing each other
Solution:
# Visualise the dependency graph# Break cycles with separate rule resources Break cycles with separate rule resources
resource "aws_security_group" "a" {
name = "sg-a"
# Don't reference sg-b here
}
resource "aws_security_group_rule" "a_to_b" {
security_group_id = aws_security_group.a.id
source_security_group_id = aws_security_group.b.id
type = "ingress"
# ...
}Module version conflicts
Error: “Module version requirements have changed” or unexpected module behaviour
Common causes:
- Using unpinned module versions
- Cached old module versions
- Breaking changes in module updates
Solution:
# Always pin module versions# Clear module cache and re-download# Clear module cache and re-download# Clear module cache and re-download# Clear module cache and re-download# Lock provider versions# Lock provider versions# Lock provider versionsrsions
terraform providers lock -platform=linux_amd64Conclusion
Terraform is an essential tool for modern infrastructure management. Its declarative approach, multi-cloud support, and state management make it the industry standard for Infrastructure as Code.
The fundamentals covered in this guide—HCL syntax, providers and resources, variables and outputs, state management, workspaces, data sources, and functions—provide the foundation for building consistent, maintainable infrastructure.
As you progress, explore Terraform modules for reusability, integrate with CI/CD pipelines for automated deployments, and consider Terraform Cloud or Enterprise for team collaboration and governance.
Frequently Asked Questions
Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp. It allows you to define, provision, and manage infrastructure across multiple cloud providers (AWS, Azure, GCP) and services using a declarative configuration language called HCL. Rather than manually creating resources, you describe the desired end state and Terraform determines the actions needed to achieve it.
HCL is the declarative language used to write Terraform configurations. It is designed to be both human-readable and machine-friendly. HCL uses a block-based syntax with key-value arguments, supports various data types (strings, numbers, booleans, lists, maps), and allows string interpolation and expressions to reference other values in your configuration.
Terraform state is a JSON file that tracks which real-world infrastructure resources correspond to your configuration. It enables Terraform to determine what changes need to be made when you update your configuration. State is critical for detecting drift, planning updates, and maintaining the mapping between your code and actual infrastructure. For team collaboration, state should be stored remotely with locking enabled.
The "terraform plan" command creates an execution plan that shows what changes Terraform will make to your infrastructure without actually making them. It previews creates, modifications, and deletions. The "terraform apply" command executes the plan and makes the actual changes to your infrastructure by calling provider APIs. Always review the plan output before applying.
Sensitive data in Terraform can be managed by: marking variables as sensitive (sensitive = true) to prevent them from being displayed in logs, using environment variables (TF_VAR_) for secrets, integrating with secret management tools like HashiCorp Vault or AWS Secrets Manager, never committing terraform.tfvars files containing secrets to version control, and encrypting your state file with a remote backend.
Workspaces allow you to manage multiple instances of your infrastructure using the same configuration files but with separate state files. This is useful for managing different environments like development, staging, and production. You can create workspaces with "terraform workspace new", switch between them with "terraform workspace select", and reference the current workspace in your configuration using "terraform.workspace".
References & Further Reading
- Terraform Documentation- Official HashiCorp documentation
- Terraform Registry- Providers, modules, and policies
- Terraform Language Documentation- HCL syntax and configuration
- Terraform Tutorials- Hands-on learning from HashiCorp
- Terraform Best Practices- Community best practices guide
- Terraform GitHub Repository- Source code and issue tracker

