Terraform
Infrastructure as Code tool for provisioning and managing cloud resources declaratively using HashiCorp Configuration Language (HCL).
Terraform
Infrastructure as Code tool for provisioning and managing cloud resources declaratively using HashiCorp Configuration Language (HCL).
Overview
Terraform enables you to define infrastructure in human-readable configuration files that can be versioned, reused, and shared. It supports multiple cloud providers (AWS, GCP, Azure, etc.) and on-premises infrastructure through a plugin-based architecture. Terraform maintains state to track resource mappings and metadata, enabling incremental changes and drift detection.
flowchart LR
A[Write HCL Config] --> B[terraform init]
B --> C[terraform plan]
C --> D{Review Changes}
D -->|Approve| E[terraform apply]
D -->|Reject| A
E --> F[Infrastructure Created]
F --> G[State Updated]
G -.->|Future Changes| A
HCL Syntax Basics
HashiCorp Configuration Language (HCL) is Terraform's declarative language for defining infrastructure.
Key Concepts
- Blocks - Containers for configuration (e.g.,
resource,variable,output) - Arguments - Assign values within blocks using
name = valuesyntax - Expressions - Compute or reference values (literals, references, functions)
- Comments - Single line
#or//, multi-line/* */
Common Patterns
# Block structure
block_type "label_one" "label_two" {
argument = "value"
nested_block {
nested_argument = "nested_value"
}
}
# Data types
string_value = "hello"
number_value = 42
bool_value = true
list_value = ["a", "b", "c"]
map_value = {
key1 = "value1"
key2 = "value2"
}
# String interpolation
message = "Hello, ${var.name}!"
# Heredoc syntax for multi-line strings
description = <<-EOT
This is a multi-line
string using heredoc
EOT
# Conditional expressions
instance_type = var.environment == "prod" ? "m5.large" : "t3.micro"
# For expressions
upper_names = [for name in var.names : upper(name)]
name_map = {for name in var.names : name => upper(name)}
# Splat expressions
instance_ids = aws_instance.web[*].id
Examples
# Complete resource block example
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
tags = {
Name = "WebServer-${var.environment}"
Environment = var.environment
ManagedBy = "Terraform"
}
# Dynamic block for multiple ingress rules
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
Resources and Data Sources
Resources create and manage infrastructure objects; data sources fetch information about existing resources.
Key Concepts
- Resources - Create, update, and delete infrastructure objects
- Data Sources - Read-only queries to existing infrastructure
- Meta-arguments - Special arguments like
depends_on,count,for_each,provider,lifecycle - Implicit dependencies - Terraform automatically determines order from references
flowchart TD
subgraph Resources
A[aws_vpc.main]
B[aws_subnet.public]
C[aws_instance.web]
end
subgraph Data Sources
D[aws_ami.ubuntu]
E[aws_availability_zones.available]
end
D -->|ami_id| C
E -->|zone_names| B
A -->|vpc_id| B
B -->|subnet_id| C
Common Patterns
# Resource definition
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "main-vpc"
}
}
# Data source definition
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
# Using data source in resource
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
}
# Meta-arguments
resource "aws_instance" "server" {
count = var.instance_count # Create multiple instances
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "Server-${count.index + 1}"
}
}
# for_each with map
resource "aws_iam_user" "users" {
for_each = toset(var.user_names)
name = each.value
}
# Lifecycle rules
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [tags]
}
}
# Explicit dependencies
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
depends_on = [aws_iam_role_policy.web_policy]
}
Examples
# Complete infrastructure example
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main"
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "public-${count.index + 1}"
}
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Variables and Outputs
Variables parameterise configurations; outputs expose values for use by other configurations or users.
Key Concepts
- Input Variables - Parameters for Terraform configurations
- Local Values - Named expressions for reuse within a module
- Output Values - Return values from a module
- Variable Precedence - Environment vars < terraform.tfvars < *.auto.tfvars < -var flag
Common Patterns
# Variable definition (variables.tf)
variable "region" {
description = "AWS region for resources"
type = string
default = "eu-west-1"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Instance type must be from the t3 family."
}
}
variable "environment" {
description = "Deployment environment"
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 = {}
}
variable "subnet_cidrs" {
description = "CIDR blocks for subnets"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
# Complex type
variable "ingress_rules" {
description = "List of ingress rules"
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
default = []
}
# Sensitive variable
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
# Local values (locals.tf)
locals {
common_tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
}
name_prefix = "${var.project_name}-${var.environment}"
}
# Output values (outputs.tf)
output "vpc_id" {
description = "ID of the created VPC"
value = aws_vpc.main.id
}
output "instance_public_ips" {
description = "Public IPs of all instances"
value = aws_instance.web[*].public_ip
}
output "db_connection_string" {
description = "Database connection string"
value = "postgresql://${aws_db_instance.main.endpoint}/${aws_db_instance.main.db_name}"
sensitive = true
}
Examples
# terraform.tfvars
region = "eu-west-2"
environment = "prod"
instance_type = "t3.small"
tags = {
Team = "Platform"
CostCentre = "12345"
}
# Using variables in resources
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web"
})
}
# Referencing outputs from another module
module "vpc" {
source = "./modules/vpc"
}
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_ids[0]
}
State Management
Terraform state tracks the mapping between configuration and real-world resources.
Key Concepts
- State File - JSON file storing resource metadata and mappings
- Remote State - Store state in shared location (S3, GCS, Azure Blob, Terraform Cloud)
- State Locking - Prevent concurrent modifications
- State Isolation - Separate state per environment/component
flowchart TD
subgraph "Remote State Architecture"
A[Terraform CLI] --> B[State Backend]
B --> C[(S3 Bucket)]
B --> D[(DynamoDB Lock)]
E[Team Member 1] --> A
F[Team Member 2] --> A
G[CI/CD Pipeline] --> A
end
Common Patterns
# Remote state with S3 backend (backend.tf)
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# GCS backend
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "prod"
}
}
# Azure backend
terraform {
backend "azurerm" {
resource_group_name = "tfstate"
storage_account_name = "tfstate12345"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
# Terraform Cloud backend
terraform {
cloud {
organization = "my-org" # Note: US spelling required
workspaces {
name = "my-workspace"
}
}
}
# Reading remote state from another configuration
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "vpc/terraform.tfstate"
region = "eu-west-1"
}
}
# Using remote state data
resource "aws_instance" "web" {
subnet_id = data.terraform_remote_state.vpc.outputs.public_subnet_id
}
State Commands
# List resources in state
terraform state list
# Show specific resource
terraform state show aws_instance.web
# Move resource (rename)
terraform state mv aws_instance.web aws_instance.web_server
# Remove resource from state (without destroying)
terraform state rm aws_instance.web
# Import existing resource into state
terraform import aws_instance.web i-1234567890abcdef0
# Pull remote state to local file
terraform state pull > terraform.tfstate.backup
# Push local state to remote
terraform state push terraform.tfstate
# Replace provider in state
terraform state replace-provider hashicorp/aws registry.terraform.io/hashicorp/aws
# Force unlock state (use with caution)
terraform force-unlock LOCK_ID
Modules
Modules are reusable, self-contained packages of Terraform configuration.
Key Concepts
- Root Module - Top-level configuration directory
- Child Modules - Modules called by other modules
- Module Sources - Local paths, Git repos, Terraform Registry
- Module Versioning - Pin versions for stability
flowchart TD
subgraph "Module Structure"
A[Root Module] --> B[VPC Module]
A --> C[Compute Module]
A --> D[Database Module]
B --> E[variables.tf]
B --> F[main.tf]
B --> G[outputs.tf]
C --> H[variables.tf]
C --> I[main.tf]
C --> J[outputs.tf]
end
Common Patterns
# Calling a local module
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.0.0.0/16"
environment = var.environment
project_name = var.project_name
}
# Calling a module from Terraform Registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b", "eu-west-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
single_nat_gateway = true
}
# Module from Git repository
module "vpc" {
source = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"
vpc_cidr = "10.0.0.0/16"
}
# Module from S3
module "vpc" {
source = "s3::https://s3-eu-west-1.amazonaws.com/bucket/vpc.zip"
}
# Passing outputs between modules
module "vpc" {
source = "./modules/vpc"
}
module "compute" {
source = "./modules/compute"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
}
# Module with count
module "web_cluster" {
source = "./modules/web-cluster"
count = var.create_cluster ? 1 : 0
cluster_name = "web"
}
# Module with for_each
module "buckets" {
source = "./modules/s3-bucket"
for_each = toset(["logs", "data", "backups"])
bucket_name = "${var.project}-${each.value}"
}
Module Structure Example
# modules/vpc/variables.tf
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
}
variable "environment" {
description = "Environment name"
type = string
}
# 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
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, 1)
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public"
}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_id" {
description = "ID of the public subnet"
value = aws_subnet.public.id
}
Workspaces
Workspaces enable multiple state files for the same configuration.
Key Concepts
- Default Workspace - Created automatically, named "default"
- Named Workspaces - Separate state for different environments
- State Isolation - Each workspace has its own state file
- Workspace Interpolation - Access current workspace via
terraform.workspace
Common Commands
# List workspaces
terraform workspace list
# Create new workspace
terraform workspace new staging
# Select workspace
terraform workspace select prod
# Show current workspace
terraform workspace show
# Delete workspace
terraform workspace delete staging
Common Patterns
# Using workspace in configuration
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
tags = {
Name = "web-${terraform.workspace}"
Environment = terraform.workspace
}
}
# Workspace-specific variables
locals {
environment_config = {
dev = {
instance_type = "t3.micro"
instance_count = 1
}
staging = {
instance_type = "t3.small"
instance_count = 2
}
prod = {
instance_type = "t3.large"
instance_count = 3
}
}
config = local.environment_config[terraform.workspace]
}
resource "aws_instance" "web" {
count = local.config.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = local.config.instance_type
}
# Backend configuration with workspaces
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "app/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
workspace_key_prefix = "workspaces"
}
}
# State stored at: workspaces/{workspace}/app/terraform.tfstate
Providers and Provisioners
Providers interact with cloud platforms and services; provisioners execute actions on resources.
Key Concepts
- Providers - Plugins that interact with APIs (AWS, GCP, Azure, etc.)
- Provider Configuration - Authentication, region, and other settings
- Provider Aliases - Multiple configurations for the same provider
- Provisioners - Last resort for bootstrapping (prefer cloud-init/user data)
Common Patterns
# Provider configuration
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
google = {
source = "hashicorp/google"
version = "~> 7.0"
}
}
required_version = ">= 1.5.0"
}
# AWS provider
provider "aws" {
region = var.aws_region
default_tags {
tags = {
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
# Provider alias for multi-region
provider "aws" {
alias = "us_east"
region = "us-east-1"
}
provider "aws" {
alias = "eu_west"
region = "eu-west-1"
}
# Using provider alias
resource "aws_instance" "us_web" {
provider = aws.us_east
ami = "ami-12345678"
instance_type = "t3.micro"
}
resource "aws_instance" "eu_web" {
provider = aws.eu_west
ami = "ami-87654321"
instance_type = "t3.micro"
}
# Assume role configuration
provider "aws" {
region = "eu-west-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformRole"
session_name = "TerraformSession"
}
}
# Provisioners (use sparingly)
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
# File provisioner
provisioner "file" {
source = "scripts/setup.sh"
destination = "/tmp/setup.sh"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
# Remote-exec provisioner
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/setup.sh",
"/tmp/setup.sh"
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
# Local-exec provisioner
provisioner "local-exec" {
command = "echo ${self.private_ip} >> private_ips.txt"
}
# Destroy-time provisioner
provisioner "local-exec" {
when = destroy
command = "echo 'Instance ${self.id} destroyed' >> destroy.log"
}
}
# Prefer user_data over provisioners
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
EOF
}
Common Commands
Essential Terraform CLI commands for daily operations.
Initialisation and Planning
# Initialise working directory
terraform init
# Reinitialise and upgrade providers
terraform init -upgrade
# Initialise with backend configuration
terraform init -backend-config="bucket=my-state-bucket"
# Create execution plan
terraform plan
# Save plan to file
terraform plan -out=tfplan
# Plan for specific target
terraform plan -target=aws_instance.web
# Plan with variable
terraform plan -var="environment=prod"
# Plan with variable file
terraform plan -var-file="prod.tfvars"
# Show plan in JSON format
terraform plan -json
Apply and Destroy
# Apply changes
terraform apply
# Apply saved plan
terraform apply tfplan
# Auto-approve (use in CI/CD)
terraform apply -auto-approve
# Apply specific target
terraform apply -target=aws_instance.web
# Replace a resource
terraform apply -replace=aws_instance.web
# Destroy all resources
terraform destroy
# Destroy specific resource
terraform destroy -target=aws_instance.web
# Destroy with auto-approve
terraform destroy -auto-approve
Validation and Formatting
# Validate configuration
terraform validate
# Format configuration
terraform fmt
# Format recursively
terraform fmt -recursive
# Check formatting (CI/CD)
terraform fmt -check
# Show formatting diff
terraform fmt -diff
Information and Debugging
# Show outputs
terraform output
# Show specific output
terraform output vpc_id
# Output in JSON
terraform output -json
# Show providers
terraform providers
# Show provider versions
terraform version
# Generate resource graph
terraform graph | dot -Tpng > graph.png
# Enable debug logging
export TF_LOG=DEBUG
terraform apply
# Console for expression testing
terraform console
Best Practices
Guidelines for maintainable, scalable, and secure Terraform configurations.
Project Structure
project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── compute/
│ └── database/
└── README.md
Configuration Best Practices
# Pin provider versions
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # Allow minor/patch updates within 6.x
}
}
required_version = ">= 1.5.0"
}
# Use meaningful resource names
resource "aws_instance" "web_server" { # Good
# ...
}
resource "aws_instance" "instance1" { # Bad
# ...
}
# Tag all resources consistently
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
Owner = var.team_name
}
}
resource "aws_instance" "web" {
tags = merge(local.common_tags, {
Name = "${var.project_name}-web"
Role = "WebServer"
})
}
# Use data sources for dynamic values
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# Avoid hardcoding
resource "aws_s3_bucket" "logs" {
bucket = "${var.project_name}-logs-${data.aws_caller_identity.current.account_id}"
}
# Use count/for_each for similar resources
resource "aws_subnet" "private" {
for_each = var.private_subnets
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
tags = {
Name = each.key
}
}
Security Best Practices
# Never commit secrets - use variables
variable "db_password" {
type = string
sensitive = true
}
# Use IAM roles instead of access keys
provider "aws" {
region = "eu-west-1"
# No access keys - uses instance profile or environment
}
# Enable encryption
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
# Use security groups with least privilege
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = var.allowed_cidrs # Not 0.0.0.0/0
}
}
State Management Best Practices
# Always use remote state with locking
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "project/env/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# Separate state per environment/component
# dev: project/dev/terraform.tfstate
# prod: project/prod/terraform.tfstate
# Use terraform_remote_state for cross-stack references
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "vpc/terraform.tfstate"
region = "eu-west-1"
}
}
Quick Reference
| Command | Description |
|---|---|
terraform init |
Initialise working directory |
terraform plan |
Preview changes |
terraform apply |
Apply changes |
terraform destroy |
Destroy resources |
terraform fmt |
Format configuration |
terraform validate |
Validate configuration |
terraform output |
Show outputs |
terraform state list |
List resources in state |
terraform state show <resource> |
Show resource details |
terraform import <resource> <id> |
Import existing resource |
terraform workspace list |
List workspaces |
terraform workspace select <name> |
Switch workspace |
terraform console |
Interactive console |
terraform graph |
Generate dependency graph |
Variable Types
| Type | Example |
|---|---|
string |
"hello" |
number |
42 |
bool |
true |
list(type) |
["a", "b"] |
set(type) |
toset(["a", "b"]) |
map(type) |
{ key = "value" } |
object({...}) |
{ name = string, age = number } |
tuple([...]) |
[string, number] |
Common Functions
| Function | Example | Description |
|---|---|---|
concat |
concat(list1, list2) |
Combine lists |
merge |
merge(map1, map2) |
Combine maps |
lookup |
lookup(map, key, default) |
Get map value |
join |
join(",", list) |
Join list to string |
split |
split(",", string) |
Split string to list |
upper/lower |
upper("hello") |
Change case |
format |
format("Hello, %s!", name) |
Format string |
file |
file("path/to/file") |
Read file contents |
templatefile |
templatefile("tpl", vars) |
Render template |
cidrsubnet |
cidrsubnet("10.0.0.0/16", 8, 1) |
Calculate subnet |
try |
try(expr, default) |
Error handling |
can |
can(expr) |
Test expression |
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
Error: Provider not found |
Provider not initialised | Run terraform init |
Error: Resource already exists |
Resource exists outside Terraform | Import with terraform import or delete external resource |
Error: State lock |
Previous operation didn't release lock | Check for running operations; use terraform force-unlock if stuck |
Error: Invalid reference |
Typo or wrong resource name | Check resource names and attributes in state |
Error: Cycle detected |
Circular dependencies | Use depends_on to break cycle or restructure |
Drift detected |
Manual changes outside Terraform | Run terraform apply to reconcile or update config |
Module not found |
Wrong source path or version | Verify source path; run terraform init -upgrade |
Timeout errors |
Resource taking too long | Increase timeouts block in resource |
Permission denied |
Insufficient IAM permissions | Check AWS/cloud provider permissions |
Backend configuration changed |
Backend settings modified | Run terraform init -reconfigure or -migrate-state |
Debugging Tips
# Enable detailed logging
export TF_LOG=DEBUG
export TF_LOG_PATH=terraform.log
# Test expressions in console
terraform console
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
# Visualise dependencies
terraform graph | dot -Tpng > graph.png
# Check state for issues
terraform state list
terraform state show aws_instance.web
# Refresh state without applying
terraform refresh # Deprecated
terraform apply -refresh-only
# Taint resource for recreation
terraform taint aws_instance.web # Deprecated
terraform apply -replace=aws_instance.web
Related Topics
The following topics would complement this Terraform cheatsheet:
- Ansible - Configuration management and application deployment; often used alongside Terraform for post-provisioning configuration
- AWS/GCP/Azure - Cloud provider-specific resources and services that Terraform manages
- Vault (HashiCorp) - Secrets management for storing and injecting sensitive values into Terraform configurations
- Packer - Machine image creation; images are then deployed via Terraform
- Terragrunt - Wrapper for Terraform that provides extra tools for working with multiple Terraform modules
- GitOps/CI-CD Patterns - Automating Terraform workflows in continuous delivery pipelines