Terraform Style Guide
Generate and maintain Terraform code following HashiCorp's official style conventions.
Reference: HashiCorp Terraform Style Guide
Supplementary references (load as needed):
- Modules: See references/modules.md for module structure, versioning,
movedblocks, and reusable module design - Dynamic resources: See references/dynamic-resources.md for decision guide on
for_eachvscountvsdynamicblocks - Error prevention: See references/error-prevention.md for anti-patterns, lifecycle rules, state backends, import blocks, and recovery commands
Code Generation Strategy
When generating Terraform code:
- Start with provider configuration and version constraints
- Create data sources before dependent resources
- Build resources in dependency order
- Add outputs for key resource attributes
- Use variables for all configurable values
File Organization
| File | Purpose |
|---|---|
terraform.tf |
Terraform and provider version requirements |
providers.tf |
Provider configurations |
main.tf |
Primary resources and data sources |
variables.tf |
Input variable declarations (alphabetical) |
outputs.tf |
Output value declarations (alphabetical) |
locals.tf |
Local value declarations |
For modules, add README.md and follow the structure in references/modules.md.
Code Formatting
Align equals signs for consecutive arguments. Place meta-arguments first, then arguments, then nested blocks, with lifecycle last:
resource "aws_instance" "example" {
# Meta-arguments first
count = 3
# Arguments (aligned =)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# Nested blocks
root_block_device {
volume_size = 20
}
# Lifecycle last
lifecycle {
create_before_destroy = true
}
}
Naming Conventions
- Lowercase with underscores for all names
- Descriptive nouns excluding the resource type
- Singular, not plural
- Default to
mainwhen only one instance exists and a specific name adds no clarity
# Bad
resource "aws_instance" "webAPI-aws-instance" {}
resource "aws_instance" "web_apis" {}
variable "name" {}
# Good
resource "aws_instance" "web_api" {}
resource "aws_vpc" "main" {}
variable "application_name" {}
Variables and Outputs
Every variable requires type and description. Every output requires description. Mark secrets with sensitive = true:
variable "instance_type" {
description = "EC2 instance type for the web server"
type = string
default = "t2.micro"
validation {
condition = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
error_message = "Instance type must be t2.micro, t2.small, or t2.medium."
}
}
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
Dynamic Resource Creation
Prefer for_each over count for multiple named resources. Use count only for conditional creation (0 or 1). For the full decision guide and dynamic block patterns, see references/dynamic-resources.md.
# for_each — stable keys, safe to add/remove
resource "aws_instance" "web" {
for_each = toset(["web-1", "web-2", "web-3"])
instance_type = "t2.micro"
tags = { Name = each.key }
}
# count — conditional creation only
resource "aws_cloudwatch_metric_alarm" "cpu" {
count = var.enable_monitoring ? 1 : 0
alarm_name = "high-cpu-usage"
threshold = 80
}
Modules
Use modules to encapsulate reusable infrastructure. Pin registry modules with version, git modules with ref:
module "vpc" {
source = "hashicorp/vpc/aws"
version = "~> 5.0"
cidr_block = var.vpc_cidr
environment = var.environment
}
For module structure, naming, versioning, moved blocks, and input/output design, see references/modules.md.
Security Best Practices
Apply these defaults when generating code:
- Enable encryption at rest (KMS/SSE)
- Configure private networking where applicable
- Apply least-privilege security groups and IAM policies
- Never hardcode credentials — use environment variables or IAM roles
- Mark sensitive outputs with
sensitive = true - Enable versioning on storage resources
Version Constraints
Pin both Terraform and provider versions in terraform.tf:
terraform {
required_version = ">= 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Constraint operators: = 1.0.0 (exact), >= 1.0.0 (minimum), ~> 1.0 (allow rightmost increment), >= 1.0, < 2.0 (range).
Provider Configuration
provider "aws" {
region = "us-west-2"
default_tags {
tags = {
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
Error Prevention
- Always use a remote backend with state locking for team projects
- Run
terraform plan -out=tfplanand review before applying saved plans - Use
lifecycle { prevent_destroy = true }on critical stateful resources - Prefer implicit dependencies over
depends_on - Never run concurrent applies against the same state file
For anti-patterns, dependency cycle resolution, state backend configs, import/check blocks, and recovery commands, see references/error-prevention.md.
Validation
Run before every commit:
terraform fmt -recursive # Format
terraform validate # Syntax + type check
Additional tools: tflint (linting), checkov/tfsec (security scanning).
Code Review Checklist
- Formatted with
terraform fmt - Validated with
terraform validate - Files follow standard organization
- All variables have
typeanddescription - All outputs have
description - Resource names: descriptive, lowercase, underscores, singular
- Version constraints pinned for Terraform and providers
- Sensitive values marked
sensitive = true - No hardcoded credentials or secrets
- Security defaults applied (encryption, private networking, least privilege)
- Modules pinned to specific versions
Based on: HashiCorp Terraform Style Guide