Terraform Style Guide
Generate and maintain Terraform code following HashiCorp's official style conventions and best practices.
Reference: HashiCorp Terraform Style Guide
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 |
Example Structure
hcl# terraform.tf terraform { required_version = ">= 1.14" required_providers { aws = { source = "hashicorp/aws" version = "~> 6.0" } } } # variables.tf variable "environment" { description = "Target deployment environment" type = string validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Environment must be dev, staging, or prod." } } # locals.tf locals { common_tags = { Environment = var.environment ManagedBy = "Terraform" } } # main.tf resource "aws_vpc" "main" { cidr_block = var.vpc_cidr enable_dns_hostnames = true tags = merge(local.common_tags, { Name = "${var.project_name}-${var.environment}-vpc" }) } # outputs.tf output "vpc_id" { description = "ID of the created VPC" value = aws_vpc.main.id }
Code Formatting
Indentation and Alignment
- Use two spaces per nesting level (no tabs)
- Align equals signs for consecutive arguments
hclresource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" subnet_id = "subnet-12345678" tags = { Name = "web-server" Environment = "production" } }
Block Organization
Arguments precede blocks, with meta-arguments first:
hclresource "aws_instance" "example" { # Meta-arguments count = 3 # Arguments ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" # Blocks root_block_device { volume_size = 20 } # Lifecycle last lifecycle { create_before_destroy = true } }
Naming Conventions
- Use lowercase with underscores for all names
- Use descriptive nouns excluding the resource type
- Be specific and meaningful
- Resource names must be singular, not plural
- Default to
mainfor resources where a specific descriptive name is redundant or unavailable, provided only one instance exists
hcl# 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
Every variable must include type and description:
hclvariable "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." } } variable "database_password" { description = "Password for the database admin user" type = string sensitive = true }
Outputs
Every output must include description:
hcloutput "instance_id" { description = "ID of the EC2 instance" value = aws_instance.web.id } output "database_password" { description = "Database administrator password" value = aws_db_instance.main.password sensitive = true }
Dynamic Resource Creation
Prefer for_each over count
hcl# Bad - count for multiple resources resource "aws_instance" "web" { count = var.instance_count tags = { Name = "web-${count.index}" } } # Good - for_each with named instances variable "instance_names" { type = set(string) default = ["web-1", "web-2", "web-3"] } resource "aws_instance" "web" { for_each = var.instance_names tags = { Name = each.key } }
count for Conditional Creation
hclresource "aws_cloudwatch_metric_alarm" "cpu" { count = var.enable_monitoring ? 1 : 0 alarm_name = "high-cpu-usage" threshold = 80 }
Security Best Practices
Refer to SECURITY.md. It includes guidance on encrypting resources, preventing sensitive data in state, and secure configurations.
Version Pinning
hclterraform { required_version = ">= 1.14" required_providers { aws = { source = "hashicorp/aws" version = "~> 6.0" } } }
Use the latest major version of each provider and the latest minor version of Terraform, unless otherwise constrained by a dependency lock file or by other modules used by the configuration.
Version constraint operators:
= 1.0.0- Exact version>= 1.0.0- Greater than or equal~> 1.0- Allow rightmost component to increment>= 1.0, < 2.0- Version range
Provider Configuration
hclprovider "aws" { region = "us-west-2" default_tags { tags = { ManagedBy = "Terraform" Project = var.project_name } } } # Aliased provider for multi-region provider "aws" { alias = "east" region = "us-east-1" }
Version Control
Never commit:
terraform.tfstate,terraform.tfstate.backup.terraform/directory*.tfplan.tfvarsfiles with sensitive data
Always commit:
- All
.tfconfiguration files .terraform.lock.hcl(dependency lock file)
Validation Tools
Run before committing:
bashterraform fmt -recursive terraform validate
Additional tools:
tflint- Linting and best practicescheckov/tfsec- Security scanning
Code Review Checklist
- Code formatted with
terraform fmt - Configuration validated with
terraform validate - Files organized according to standard structure
- All variables have type and description
- All outputs have descriptions
- Resource names use descriptive nouns with underscores
- Version constraints pinned explicitly
- Sensitive values marked with
sensitive = true - No hardcoded credentials or secrets
- Security best practices applied
Based on: HashiCorp Terraform Style Guide


