intermediate devops Terraform 1.14.8 · Updated April 2026

Terraform Basics Cheatsheet

Quickly learn Terraform's core concepts, essential commands, and best practices for managing infrastructure as code.

· 8 min read · AI-reviewed

Quick Overview

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

Getting Started

Let’s get Terraform installed and run your first “Hello, World!” equivalent by creating a local file.

  1. Install Terraform:

    • macOS: Use Homebrew as shown above.
    • Linux (Debian/Ubuntu):
      # 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
    • Windows: Download the appropriate .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.
  2. Verify Installation:

    terraform version

    You should see output similar to Terraform v1.14.8.

  3. 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."
    }
  4. 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.

  5. 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.

  6. 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.

  7. Destroy the Resources: Clean up any resources created by Terraform:

    # Destroy all resources defined in the configuration
    terraform destroy

    Type yes when prompted.

Core Concepts

Terraform operates on several key concepts that form its mental model.

ConceptDescription
ProviderA 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.
ResourceA 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 SourceAllows 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.
ModuleA reusable, self-contained package of Terraform configurations. Modules encapsulate common infrastructure patterns, promoting reusability and maintainability. Every Terraform configuration is, itself, a root module.
StateTerraform 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.
HCLHashiCorp Configuration Language. The domain-specific language used by Terraform to write configurations. It’s designed to be human-readable and machine-friendly.
PlanAn 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.

Essential Commands

These commands form the core lifecycle of a Terraform project.

Project Initialization & Formatting

# 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

Infrastructure Lifecycle

# 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

State Management & Inspection

# 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

Common Patterns

Terraform’s power comes from structuring your configurations for reusability and maintainability.

Modular Architecture

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.

Environment-Based Deployments

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.

Gotchas & Tips

Avoid common pitfalls to keep your Terraform projects robust and maintainable.

  • Immutable Attributes Lead to Recreation: Be aware that changing certain resource attributes (e.g., an AWS EC2 instance type or an S3 bucket name) will cause Terraform to destroy the existing resource and create a new one, leading to downtime or data loss. Always review terraform plan output carefully for (forces replacement) messages.
    • Tip: Use modular design to isolate destructive changes. Move mutable parameters to separate resources if possible.
  • Managing State: The terraform.tfstate file is critical.
    • Don’t modify it manually unless you absolutely know what you’re doing, and only after backing it up.
    • Store state remotely (e.g., S3, Azure Blob Storage, HCP Terraform) and enable locking for collaborative environments to prevent concurrent modifications and data corruption.
  • Sensitive Data: Never commit sensitive information (API keys, passwords) directly into your .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.
  • Hardcoding Providers in Modules: Avoid defining 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.
  • Inline Blocks vs. Separate Resources: For resources like security group rules, prefer standalone 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.
  • File Paths in Modules: When a module references local files (e.g., user data scripts), use 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.

Next Steps


Source: z2h.fyi/cheatsheets/terraform-basics — Zero to Hero cheatsheets for developers.

Source: z2h.fyi/cheatsheets/terraform-basics — Zero to Hero cheatsheets for developers.