name: "terraform" description: 'Provision cloud infrastructure safely and consistently using Terraform and Infrastructure as Code patterns. Use when writing .tf or .tfvars files, creating reusable modules, managing remote state, securing infrastructure, or running Terratest integration tests.' metadata: author: "AgentX" version: "1.0.0" created: "2026-02-26" updated: "2026-02-26" compatibility: providers: ["azure", "aws", "gcp"] platforms: ["windows", "linux", "macos"]
Terraform / Infrastructure as Code
Purpose: Best practices for provisioning cloud resources with Terraform, covering code style, file layout, naming, state management, modules, security, and testing.
When to Use This Skill
- Writing or editing
.tfor.tfvarsfiles - Creating reusable Terraform modules
- Managing remote state backends
- Securing infrastructure definitions
- Running Terratest or
terraform validate/terraform plan
Decision Tree
Terraform Decision
+-- New infrastructure project?
| +-- Azure only? -> Consider Bicep as alternative
| +-- Multi-cloud or AWS/GCP? -> Use Terraform
| +-- Existing Terraform codebase? -> Use Terraform
+-- Structuring code?
| +-- Single environment? -> One root module with terraform.tfvars
| +-- Multiple environments? -> Workspaces or separate root dirs per env
| +-- Shared patterns? -> Extract reusable modules/ with versioning
+-- Managing state?
| +-- Team project? -> Remote backend with locking (S3/Azure Storage/GCS)
| +-- Solo developer? -> Remote backend still recommended
| +-- Never -> Local .tfstate in production
+-- Validating changes?
| +-- Syntax? -> terraform validate
| +-- Drift detection? -> terraform plan
| +-- Integration tests? -> Terratest (Go)
Code Style
- Use Terraform 1.5+ features (import blocks,
checkblocks,movedblocks) - Maximum line length: 120 characters
- Use
terraform fmtfor formatting (enforced via pre-commit) - Use
tflintfor linting
File Organization
infra/
+-- main.tf # Provider config, data sources
+-- variables.tf # Input variable declarations
+-- outputs.tf # Output value declarations
+-- terraform.tfvars # Variable values (gitignored in prod)
+-- locals.tf # Local values and computed expressions
+-- versions.tf # Required providers and versions
-- modules/
-- networking/ # Reusable module
+-- main.tf
+-- variables.tf
-- outputs.tf
Naming Conventions
| Resource | Convention | Example |
|---|---|---|
| Resource names | snake_case | azurerm_resource_group.main |
| Variable names | snake_case | var.resource_group_name |
| Output names | snake_case | output.storage_account_id |
| Module names | kebab-case dirs | modules/app-service/ |
| Tags | PascalCase keys | Environment = "Production" |
Resource Definitions
# MUST: Use descriptive resource names (not generic "this" or "main")
resource "azurerm_resource_group" "app_rg" {
name = "${var.project_name}-${var.environment}-rg"
location = var.location
tags = local.common_tags
}
# MUST: Pin provider versions
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
Variables
# MUST: Add description, type, and validation to all variables
variable "environment" {
description = "Deployment environment (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
# MUST: Use sensitive = true for secrets
variable "db_password" {
description = "Database administrator password"
type = string
sensitive = true
}
# SHOULD: Provide defaults where safe
variable "location" {
description = "Azure region for resources"
type = string
default = "eastus2"
}
State Management
- MUST use remote state backend (Azure Storage, S3, GCS) - never local state in production
- MUST enable state locking
- MUST NOT commit
.tfstatefiles or.tfvarswith secrets
# Remote state backend (Azure)
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
Modules
- SHOULD create reusable modules for repeated patterns
- MUST version-pin module sources
- MUST document every module input/output
module "app_service" {
source = "./modules/app-service"
name = "${var.project_name}-${var.environment}"
resource_group_name = azurerm_resource_group.app_rg.name
location = var.location
sku_name = var.environment == "prod" ? "P1v3" : "B1"
tags = local.common_tags
}
Security
- MUST NOT hardcode secrets in
.tfor.tfvarsfiles - MUST use Key Vault references or
sensitivevariables - SHOULD use managed identity instead of service principal keys
- MUST enable HTTPS and encryption at rest for all applicable resources
- SHOULD run
checkovortfsecfor security scanning
Testing
- Use
terraform validatefor syntax checking - Use
terraform planfor drift detection - Use Terratest (Go) for integration testing
- Name test files:
*_test.go
Core Rules
- Pin Provider Versions - Always specify
version = "~> X.0"in required_providers; never use unversioned providers - Remote State Only - Use a remote backend with locking for all environments; never commit
.tfstatefiles - Validate All Variables - Every variable MUST have
description,type, andvalidationblocks where applicable - Mark Secrets Sensitive - Use
sensitive = trueon all password, key, and token variables - Use Modules for Reuse - Extract repeated resource patterns into
modules/with documented inputs and outputs - Format and Lint - Run
terraform fmtandtflintbefore every commit; enforce via pre-commit hooks - Plan Before Apply - Always run
terraform planand review changes beforeterraform applyin CI/CD - Descriptive Resource Names - Use meaningful snake_case resource names (e.g.,
app_rg), not genericthisormain
Anti-Patterns
- Local State in Production: Using local
.tfstatefor team projects -> Use remote backend with locking - Hardcoded Secrets: Passwords or keys in
.tf/.tfvarsfiles -> Use Key Vault references orsensitivevariables - Unpinned Providers: No version constraint on providers -> Pin with
~>operator to major version - No Validation Blocks: Accepting any input without checks -> Add
validation {}blocks to variables - Monolithic Root Module: All resources in one
main.tf-> Split into logical files and extract modules - Apply Without Plan: Running
terraform applywithout reviewing plan output -> Always plan first, review diff