Terraform Basics Cheatsheet
Quickly learn Terraform's core concepts, essential commands, and best practices for managing infrastructure as code.
Quickly learn Terraform's core concepts, essential commands, and best practices for managing infrastructure as code.
Terraform is an open-source Infrastructure as Code (IaC) tool by HashiCorp that enables you to define, provision, and manage cloud and on-premises resources using a declarative configuration language. It allows you to describe your desired infrastructure state in human-readable files (HCL), then automates the process of reaching and maintaining that state across various providers like AWS, Azure, GCP, and more. Terraform helps teams manage infrastructure consistently, reduce manual errors, and accelerate deployment cycles.
Current Stable Version: 1.14.8 (as of March 25, 2026)
One-Line Install (macOS via Homebrew):
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Let’s get Terraform installed and run your first “Hello, World!” equivalent by creating a local file.
Install Terraform:
# Install the HashiCorp GPG key
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
# Add the HashiCorp repository
echo "deb [arch=$(dpkg --print-architecture) 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
# Update and install Terraform
sudo apt update && sudo apt install terraform
.zip package from the official Terraform releases page. Unzip the terraform.exe binary to a directory (e.g., C:\terraform) and add that directory to your system’s PATH environment variable.Verify Installation:
terraform version
You should see output similar to Terraform v1.14.8.
Your First Configuration (Hello World):
Create a new directory (e.g., my-first-tf) and inside it, create a file named main.tf with the following content:
# main.tf
# This resource simply runs a local command during the apply phase.
resource "null_resource" "hello_world" {
provisioner "local-exec" {
command = "echo 'Hello, World! from Terraform'"
}
}
# Define an output to show a message after applying
output "message" {
value = "Terraform applied successfully and said hello!"
description = "A friendly confirmation message."
}
Initialize the Working Directory:
Navigate to your my-first-tf directory in your terminal and run:
# Initialize the project, downloading any necessary providers
terraform init
This command downloads the null provider, which is implicitly required by null_resource.
Plan the Changes: See what Terraform will do before making any actual changes:
# Show the execution plan
terraform plan
This will output a plan showing that one null_resource will be added.
Apply the Changes: Execute the plan to create the resource (or run the command in this case):
# Apply the planned changes
terraform apply
Type yes when prompted to confirm the action. You’ll see “Hello, World! from Terraform” printed to your console, followed by the output message.
Destroy the Resources: Clean up any resources created by Terraform:
# Destroy all resources defined in the configuration
terraform destroy
Type yes when prompted.
Terraform operates on several key concepts that form its mental model.
| Concept | Description |
|---|---|
| Provider | A plugin that interacts with an API (e.g., AWS, Azure, GCP, Kubernetes, GitHub) to understand resources and perform actions. You configure providers to authenticate and specify desired regions/endpoints. |
| Resource | A block that defines a piece of infrastructure (e.g., a virtual machine, a network, a database, an S3 bucket). Resources have attributes that define their desired state. |
| Data Source | Allows Terraform to fetch information about existing infrastructure or external data (e.g., existing VPC ID, latest AMI ID). This lets you use resources not managed by the current Terraform configuration. |
| Module | A reusable, self-contained package of Terraform configurations. Modules encapsulate common infrastructure patterns, promoting reusability and maintainability. Every Terraform configuration is, itself, a root module. |
| State | Terraform maintains a state file (usually terraform.tfstate) that maps real-world infrastructure resources to your configuration. It tracks metadata, resource dependencies, and the current status of your managed infrastructure. Crucial for planning changes. |
| HCL | HashiCorp Configuration Language. The domain-specific language used by Terraform to write configurations. It’s designed to be human-readable and machine-friendly. |
| Plan | An execution plan is generated by Terraform to show what actions it will take (create, update, destroy) to move the infrastructure from its current state to the desired state defined in your configuration. |
These commands form the core lifecycle of a Terraform project.
# Initialize a new or existing Terraform working directory.
# Downloads provider plugins, sets up backend (local or remote state).
terraform init
# Validate the syntax and configuration files in the current directory.
# Does not connect to the cloud provider.
terraform validate
# Rewrite Terraform configuration files to a canonical format.
# Improves readability and consistency.
terraform fmt
# Generate and show an execution plan.
# This preview shows what actions Terraform will take without applying them.
terraform plan
# Apply the changes required to reach the desired state of the configuration.
# Prompts for confirmation unless `-auto-approve` is used.
terraform apply
# Destroy all resources managed by the current Terraform configuration.
# Prompts for confirmation unless `-auto-approve` is used.
terraform destroy
# List all resources currently tracked in the Terraform state file.
terraform state list
# Show the attributes of a single resource as stored in the state file.
# Replace 'aws_s3_bucket.my_bucket' with your resource address.
terraform state show aws_s3_bucket.my_bucket
# Output values from your root module.
# Replace 'bucket_name' with the name of your output.
terraform output bucket_name
Terraform’s power comes from structuring your configurations for reusability and maintainability.
Break down your infrastructure into reusable modules. Each module should encapsulate a specific component (e.g., networking, compute, storage). This promotes the DRY (Don’t Repeat Yourself) principle.
# modules/vpc/main.tf
# A simple VPC module
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
tags = {
Name = "my-vpc-${var.environment}"
}
}
variable "cidr_block" {
description = "The CIDR block for the VPC."
type = string
}
variable "environment" {
description = "The environment name (dev, staging, prod)."
type = string
}
output "vpc_id" {
value = aws_vpc.main.id
description = "The ID of the created VPC."
}
# root_module/main.tf
# Calling the VPC module for a 'dev' environment
provider "aws" {
region = "us-east-1"
}
module "dev_vpc" {
source = "./modules/vpc" # Local path to your module
cidr_block = "10.0.0.0/16"
environment = "dev"
}
output "dev_vpc_id" {
value = module.dev_vpc.vpc_id
}
Tip: Modules should be provider-agnostic. Let the calling (root) module define and pass the required providers, rather than hardcoding them inside the module itself.
Organize your configurations to manage multiple environments (dev, staging, prod) effectively, often leveraging modules. This usually involves separate directories for each environment, with each directory calling shared modules and passing environment-specific variables.
project/
├── modules/
│ └── vpc/
│ └── main.tf
│ └── variables.tf
│ └── outputs.tf
├── environments/
│ ├── dev/
│ │ └── main.tf
│ │ └── variables.tf
│ ├── staging/
│ │ └── main.tf
│ │ └── variables.tf
│ └── prod/
│ └── main.tf
│ └── variables.tf
Each environments/*/main.tf would call the modules/vpc (and other shared modules) with appropriate variable values for its respective environment.
Avoid common pitfalls to keep your Terraform projects robust and maintainable.
terraform plan output carefully for (forces replacement) messages.
terraform.tfstate file is critical.
.tf files. Use environment variables, a secrets manager (like HashiCorp Vault, AWS Secrets Manager, Azure Key Vault), or Terraform’s built-in sensitive attribute for outputs.count vs. for_each: Misusing or changing between count and for_each, or altering keys/indices, can lead to Terraform losing track of resources and recreating them unnecessarily. Use for_each when you have a map or set of unique strings/objects for stable identifiers.provider blocks directly within your modules. This couples the module to a specific account or region, limiting reusability. Instead, let the root module define and pass provider configurations to the modules.aws_security_group_rule resources over inline ingress/egress blocks within aws_security_group. This allows for greater flexibility and composability, enabling module users to inject additional rules without modifying the module’s source.path.module or path.root to ensure portability, rather than relying on path.cwd (current working directory), which can vary based on where Terraform is executed.Source: z2h.fyi/cheatsheets/terraform-basics — Zero to Hero cheatsheets for developers.