This page is generated from skills/terraform-skill/references/quick-reference.md. Edit the source, not this page.
This skill is maintained by Anton Babenko (terraform-best-practices.com, Compliance.tf) under the Apache-2.0 license. Upstream: https://github.com/antonbabenko/terraform-skill
Quick Reference
Part of: terraform-skill Purpose: Command cheat sheets and decision flowcharts
This document provides quick lookup tables, command references, and decision flowcharts for rapid consultation during development.
Table of Contents
- Command Cheat Sheet
- Decision Flowchart
- Version-Specific Guidance
- Troubleshooting Guide
- Migration Paths
Command Cheat Sheet
Static Analysis
Works with both terraform and tofu commands:
# Format and validate
terraform fmt -recursive -check # or: tofu fmt -recursive -check
terraform validate # or: tofu validate
# Linting
tflint --init && tflint
# Security scanning
checkov -d .
Native Tests (1.6+)
# Run all tests
terraform test # or: tofu test
# Run tests in specific directory
terraform test -test-directory=tests/unit/
# Verbose output
terraform test -verbose
Plan Validation
# Generate and review plan
terraform plan -out tfplan # or: tofu plan -out tfplan
# Convert plan to pretty JSON
terraform show -json tfplan | jq -r '.' > tfplan.json
# Check for specific changes
terraform show tfplan | grep "will be created"
State Management
# View all resources in state
terraform state list
# Show specific resource details
terraform state show aws_instance.web
# Move/rename resource in state (refactoring)
terraform state mv aws_instance.old aws_instance.new
terraform state mv aws_instance.app module.compute.aws_instance.app
# Remove resource from state (keeps actual resource)
terraform state rm aws_instance.temporary
# Import existing resource into state
terraform import aws_instance.web i-1234567890abcdef0
# Import using import blocks (1.5+)
# Define in .tf: import { to = aws_instance.web, id = "i-123..." }
# Note: File must not exist — Terraform refuses to overwrite.
terraform plan -generate-config-out=imported.tf
# Detect configuration drift
terraform plan -refresh-only
# Update state to match reality (no infrastructure changes)
terraform apply -refresh-only
# Backup state to file
terraform state pull > backup-$(date +%Y%m%d).tfstate
# Restore state from backup (DANGEROUS)
terraform state push backup.tfstate
# Force unlock stuck state lock
# Default: prompts for y/N confirmation
terraform force-unlock LOCK_ID
# CI-friendly (skips prompt):
terraform force-unlock -force LOCK_ID
State Backend Migration
# Migrate from local to remote backend
# 1. Add backend config to backend.tf
# 2. Run migration
terraform init -migrate-state
# Change backend without migrating state
terraform init -reconfigure
# Pass backend config at runtime
terraform init \
-backend-config="key=prod/terraform.tfstate" \
-backend-config="dynamodb_table=terraform-locks"
# Or use config file
terraform init -backend-config=backend-prod.hcl
Decision Flowchart
Testing Approach Selection
Need to test Terraform/OpenTofu code?
│
├─ Just syntax/format?
│ └─ terraform/tofu validate + fmt
│
├─ Static security scan?
│ └─ trivy + checkov
│
├─ Terraform/OpenTofu 1.6+?
│ ├─ Simple logic test?
│ │ └─ Native terraform/tofu test
│ │
│ └─ Complex integration?
│ └─ Terratest
│
└─ Pre-1.6?
├─ Go team?
│ └─ Terratest
│
└─ Neither?
└─ Plan to upgrade Terraform/OpenTofu
Module Development Workflow
1. Plan
├─ Define inputs (variables.tf)
├─ Define outputs (outputs.tf)
└─ Document purpose (README.md)
2. Implement
├─ Create resources (main.tf)
├─ Pin versions (versions.tf)
└─ Add examples (examples/simple, examples/complete)
3. Test
├─ Static analysis (validate, fmt, lint)
├─ Unit tests (native or Terratest)
└─ Integration tests (examples/)
4. Document
├─ Update README with usage
├─ Document inputs/outputs
└─ Add CHANGELOG
5. Publish
├─ Tag version (git tag v1.0.0)
├─ Push to registry
└─ Announce changes
Version-Specific Guidance
Terraform 1.0-1.5
- ❌ No native testing framework
- ✅ Use Terratest
- ✅ Focus on static analysis
- ✅ terraform plan validation
Terraform 1.6+ / OpenTofu 1.6+
- ✅ NEW: Native
terraform test/tofu testframework with.tftest.hclfiles - ✅ Consider migrating simple tests from Terratest
- ✅ Keep Terratest for complex integration
- ✅ Import blocks from 1.5 available for declarative imports with
-generate-config-out
Terraform 1.7+ / OpenTofu 1.7+
- ✅ NEW: Mock providers for unit testing
- ✅ Reduce costs with mocking
- ✅ Use real integration tests for final validation
- ✅ Faster test iteration
Terraform vs OpenTofu Comparison
| Factor | Terraform | OpenTofu |
|---|---|---|
| Licensing | Business Source License 1.1 (BUSL-1.1) | Mozilla Public License 2.0 (MPL 2.0) |
| Governance | HashiCorp (single vendor) | Linux Foundation (community-driven) |
| Latest Version | 1.14+ | 1.11+ |
| Native Testing | 1.6+ | 1.6+ |
| Mock Providers | 1.7+ | 1.7+ |
| Feature Parity | Reference implementation | Compatible fork with some additions |
| Enterprise Support | HCP Terraform, Terraform Cloud | Multiple vendors |
| Migration Path | N/A | Drop-in replacement for Terraform ≤1.5.x; feature-compatible fork thereafter with divergence on encryption, mock providers, provider functions, and other post-1.6 additions. Verify specific feature availability per version. |
Choose Terraform for: HCP Terraform / Terraform Cloud, HashiCorp enterprise support, first access to latest features.
Choose OpenTofu for: open-source governance, vendor-lock-in avoidance, BUSL-1.1 incompatibility.
Since OpenTofu 1.6 the platforms have diverged — this skill notes version floors explicitly and shows both terraform and tofu commands. When creating modules, Claude asks preference to pick commands/docs.
Troubleshooting Guide
Issue: Tests fail in CI but pass locally
Symptoms:
- Tests pass on your machine
- Same tests fail in GitHub Actions/GitLab CI
Common Causes:
- Different Terraform/provider versions
- Different environment variables
- Different AWS credentials/permissions
Solution:
# versions.tf - Pin versions explicitly
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # Pin to major version
}
}
}
Issue: Parallel tests conflict
Symptoms:
- Tests fail when run in parallel
- Error: "ResourceAlreadyExistsException"
Cause: Resource naming collisions
Solution:
// Use unique identifiers
import "github.com/gruntwork-io/terratest/modules/random"
uniqueId := random.UniqueId()
bucketName := fmt.Sprintf("test-bucket-%s", uniqueId)
Issue: High test costs
Symptoms:
- AWS bill increasing from tests
- Many orphaned resources in test account
Solutions:
-
Use mocking for unit tests (Terraform 1.7+)
mock_provider "aws" { ... } -
Implement resource TTL tags
Vars: map[string]interface{}{"tags": map[string]string{"Environment": "test","TTL": "2h",},} -
Run integration tests only on main branch
if: github.ref == 'refs/heads/main' -
Use smaller instance types
instance_type = "t3.micro" # Not "m5.large" -
Share test resources when safe
- VPCs, security groups (rarely change)
- Don't share: instances, databases (change often)
Issue: State lock is stuck
Symptoms:
Error: Error acquiring the state lock
Lock Info:
ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Who: user@hostname
Created: 2026-01-20 12:00:00
Common Causes:
- Terraform process crashed or was killed
- Network interruption during operation
- CI/CD job terminated unexpectedly
Solution:
# 1. Verify the operation is NOT actually running
# Check the host mentioned in lock info
ssh user@hostname "ps aux | grep terraform"
# Or check CI/CD job status
# GitHub Actions: Check workflow runs
# GitLab CI: Check pipeline jobs
# 2. Only if confirmed the operation is not running:
terraform force-unlock LOCK_ID
# 3. Document why you unlocked
echo "Force-unlocked due to CI job timeout" > unlock-notes.txt
Prevention:
# GitHub Actions - Use concurrency control
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false # Wait, don't cancel
Issue: State file is corrupted or lost
Symptoms:
- Error: "state snapshot was created by Terraform v1.8.0"
- Error: "Failed to load state"
- State file missing or unreadable
Solutions:
If versioning enabled (S3):
# List versions
aws s3api list-object-versions \
--bucket my-terraform-state \
--prefix prod/terraform.tfstate
# Restore previous version
aws s3api get-object \
--bucket my-terraform-state \
--key prod/terraform.tfstate \
--version-id PREVIOUS_VERSION_ID \
terraform.tfstate.restored
# Push restored state
terraform state push terraform.tfstate.restored
If no backup exists:
# Recreate state by importing all resources
terraform import aws_vpc.main vpc-12345678
terraform import aws_subnet.private[0] subnet-abcd1234
# ... continue for all resources
# Or use import blocks (1.5+)
# In .tf file:
# import { to = aws_vpc.main, id = "vpc-12345678" }
# Note: File must not exist — Terraform refuses to overwrite.
terraform plan -generate-config-out=imported.tf
Issue: Configuration drift detected
Symptoms:
Note: Objects have changed outside of Terraform
Cause: Manual changes in console or by other tools
Solutions:
# View drift
terraform plan -refresh-only
# Accept drift (update state to match reality)
terraform apply -refresh-only
# Or fix drift (update resources to match config)
terraform apply
# Prevent drift with detective controls
# - Enable CloudTrail
# - Use AWS Config rules
# - Regular terraform plan in CI
Issue: Cannot migrate state between backends
Symptoms:
terraform init -migrate-statefails- Backend authentication errors
Solutions:
# Ensure credentials are configured
export AWS_PROFILE=terraform
# or
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
# Try migration again
terraform init -migrate-state
# If still failing, manual migration:
# 1. Pull state from old backend
terraform state pull > old-state.json
# 2. Switch backend config
# Edit backend.tf
# 3. Initialize new backend
terraform init -reconfigure
# 4. Push state to new backend
terraform state push old-state.json
Migration Paths
From Manual Testing → Automated
Phase 1: Static analysis
terraform validate
terraform fmt -check
Phase 2: Plan review
terraform plan -out=tfplan
# Manual review
Phase 3: Automated tests
- Native tests (1.6+)
- OR Terratest
Phase 4: CI/CD integration
- GitHub Actions / GitLab CI
- Automated apply on main branch
From Terratest → Native Tests (1.6+)
Strategy: Gradual migration
-
Keep Terratest for:
- Complex integration tests
- Multi-step workflows
- Cross-provider tests
-
Migrate to native tests:
- Simple unit tests
- Logic validation
- Mock-friendly tests
-
During transition:
- Maintain both frameworks
- Gradually increase native test coverage
- Remove Terratest tests once replaced
Example: Mixed approach
tests/
├── unit/ # Native tests
│ └── validation.tftest.hcl
└── integration/ # Terratest
└── complete_test.go
From Terraform → OpenTofu
OpenTofu is a drop-in replacement for Terraform ≤1.5.x; a feature-compatible fork thereafter with divergence on encryption, mock providers, provider functions, and other post-1.6 additions. See the Terraform vs OpenTofu Comparison and verify per-version feature availability.
- HCL ≤1.5.x — no code changes; providers and state files compatible. Verify post-1.6 features per version.
- CI/CD — swap
terraformfortofuininit/plan/applyinvocations. - Docs — note OpenTofu compatibility in README; update workflow templates to the
tofubinary.
Pre-Commit Checklist
Formatting & Validation
Run these commands before every commit:
# Format all Terraform files
terraform fmt -recursive
# Validate configuration
terraform validate
Naming Convention Review
- All identifiers use
_not- - No resource names repeat resource type (no
aws_vpc.main_vpc) - Single-instance resources named
thisor descriptive name - Variables have plural names for lists/maps (
subnet_idsnotsubnet_id) - All variables have descriptions
- All outputs have descriptions
- Output names follow
{name}_{type}_{attribute}pattern - No double negatives in variable names
Code Structure Review
-
count/for_eachat top of resource blocks (blank line after) -
tagsas last real argument in resources -
depends_onafter tags (if used) -
lifecycleat end of resource (if used) - Variables ordered: description → type → default → sensitive → nullable → validation
- Only
#comments used (no//or/* */)
Modern Features Check
- Using
try()notelement(concat()) - Secrets use write-only arguments or external data sources (not in state)
-
nullable = falseset on non-null variables -
optional()used in object types where applicable (Terraform 1.3+) - Variable validation blocks added where constraints needed
- Consider cross-variable validation for related variables (Terraform 1.9+)
Architecture Review
-
terraform.tfvarsonly at composition level (not in modules) - Remote state configured (never local state)
- Resource modules don't hardcode values (use variables/data sources)
-
terraform_remote_stateused for cross-composition dependencies - File structure follows standard: main.tf, variables.tf, outputs.tf, versions.tf
Documentation Check
Required documentation for all modules:
- README.md exists with absolute links (Terraform Registry compatibility)
- All variables documented in README with descriptions and types
- All outputs documented in README with descriptions
- Usage examples provided showing how to use the module
- Version requirements specified (Terraform version, provider versions)
Version Management Quick Reference
Constraint Syntax
| Syntax | Meaning | Use Case |
|---|---|---|
"5.0.0" | Exact version | Avoid (inflexible) |
"~> 5.0" | Pessimistic (>= 5.0, < 6.0 — any 5.x) | Allow minor and patch updates within 5.x |
"~> 5.0.1" | Pessimistic (>= 5.0.1, < 5.1.0 — 5.0.x patches) | Lock to 5.0.x patch updates only |
">= 5.0, < 6.0" | Range | Any 5.x version |
">= 5.0" | Minimum | Risky (breaking changes) |
Strategy by Component
| Component | Recommendation | Example |
|---|---|---|
| Terraform | Pin minor, allow patch | required_version = "~> 1.9" |
| Providers | Pin major, allow minor/patch | version = "~> 5.0" |
| Modules (prod) | Pin exact version | version = "5.1.2" |
| Modules (dev) | Allow patch updates | version = "~> 5.1" |
Update Workflow
# Step 1: Lock versions initially
terraform init # Creates .terraform.lock.hcl
# Step 2: Update to latest within constraints
terraform init -upgrade # Updates providers
# Step 3: Review changes
terraform plan
# Step 4: Commit lock file
git add .terraform.lock.hcl
git commit -m "Update provider versions"
Update Strategy
Security patches:
- Update immediately
- Test: dev → stage → prod
- Prioritize Terraform core and provider updates
Minor versions:
- Regular maintenance (monthly/quarterly)
- Review changelog for breaking changes
- Test thoroughly before production
Major versions:
- Planned upgrade cycles
- Dedicated testing period
- May require code changes
- Phased rollout: dev → stage → prod
Refactoring Quick Reference
Common Refactoring Patterns
Pattern 1: Count to For_Each Migration
When: Need stable resource addressing or items might be reordered
# Step 1: Add for_each, keep count commented
# Step 2: Add moved blocks for each resource
# Step 3: Run terraform plan (should show "moved" not "destroy/create")
# Step 4: Apply changes
# Step 5: Remove commented count
Key principle: Use moved blocks to preserve existing resources
Pattern 2: Legacy to Modern Terraform
0.12/0.13 → 1.x checklist:
- Replace
element(concat(...))→try() - Add
nullable = falsewhere appropriate - Use
optional()in object types (1.3+) - Add
validationblocks - Migrate secrets to write-only arguments (1.11+)
- Use
movedblocks for refactoring (1.1+) - Add cross-variable validation (1.9+)
Pattern 3: Secrets Remediation
Goal: Move secrets out of Terraform state
# Step 1: Create secret in AWS Secrets Manager (outside Terraform)
aws secretsmanager create-secret --name prod-db-password --secret-string "..."
# Step 2: Update Terraform to use data sources
# Step 3: Use write-only argument (Terraform 1.11+)
# Step 4: Remove random_password resource or variable
# Step 5: Apply and verify secret not in state
terraform show | grep -i password # Should not appear
Refactoring Decision Tree
What are you refactoring?
├─ Resource addressing (count[0] → for_each["key"])
│ └─ Use: moved blocks + for_each conversion
│
├─ Secrets in state
│ └─ Use: AWS Secrets Manager + write-only arguments (1.11+)
│
├─ Legacy Terraform syntax (0.12/0.13)
│ └─ Use: Modern feature checklist above
│
└─ Module structure (rename, reorganize)
└─ Use: moved blocks to preserve resources
Migration Best Practices
Before refactoring:
- Backup state file
- Test in development first
- Review terraform plan carefully
- Document what changed and why
During refactoring:
- One change at a time
- Verify each step with terraform plan
- Use moved blocks, not destroy/recreate
- Keep git history clean with logical commits
After refactoring:
- Verify idempotency (plan shows no changes)
- Test in staging before production
- Update documentation
- Communicate changes to team
For detailed refactoring patterns, see: Code Patterns: Refactoring Patterns
Common Patterns
Resource Naming
# ✅ Good: Descriptive, contextual
resource "aws_instance" "web_server" { }
resource "aws_s3_bucket" "application_logs" { }
# ❌ Bad: Generic
resource "aws_instance" "main" { }
resource "aws_s3_bucket" "bucket" { }
Variable Naming
# ✅ Good: Context-specific
var.vpc_cidr_block
var.database_instance_class
# ❌ Bad: Generic
var.cidr
var.instance_class
File Organization
Standard module structure:
├── main.tf # Primary resources
├── variables.tf # Input variables
├── outputs.tf # Output values
├── versions.tf # Provider versions
└── README.md # Documentation
Back to: Main Skill File