home / skills / hashicorp / agent-skills / terraform-style-guide

This skill helps you generate Terraform configurations following HashiCorp style, ensuring proper structure, naming, and security best practices.

npx playbooks add skill hashicorp/agent-skills --skill terraform-style-guide

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.3 KB
---
name: terraform-style-guide
description: Generate Terraform HCL code following HashiCorp's official style conventions and best practices. Use when writing, reviewing, or generating Terraform configurations.
---

# Terraform Style Guide

Generate and maintain Terraform code following HashiCorp's official style conventions and best practices.

**Reference:** [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)

## Code Generation Strategy

When generating Terraform code:

1. Start with provider configuration and version constraints
2. Create data sources before dependent resources
3. Build resources in dependency order
4. Add outputs for key resource attributes
5. 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.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.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

```hcl
resource "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:

```hcl
resource "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

```hcl
# Bad
resource "aws_instance" "webAPI-aws-instance" {}
variable "name" {}

# Good
resource "aws_instance" "web_api" {}
variable "application_name" {}
```

## Variables

Every variable must include `type` and `description`:

```hcl
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."
  }
}

variable "database_password" {
  description = "Password for the database admin user"
  type        = string
  sensitive   = true
}
```

## Outputs

Every output must include `description`:

```hcl
output "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

```hcl
resource "aws_cloudwatch_metric_alarm" "cpu" {
  count = var.enable_monitoring ? 1 : 0

  alarm_name = "high-cpu-usage"
  threshold  = 80
}
```

## Security Best Practices

When generating code, apply security hardening:

- Enable encryption at rest by default
- Configure private networking where applicable
- Apply principle of least privilege for security groups
- Enable logging and monitoring
- Never hardcode credentials or secrets
- Mark sensitive outputs with `sensitive = true`

### Example: Secure S3 Bucket

```hcl
resource "aws_s3_bucket" "data" {
  bucket = "${var.project}-${var.environment}-data"
  tags   = local.common_tags
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id

  versioning_configuration {
    status = "Enabled"
  }
}

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"
      kms_master_key_id = aws_kms_key.s3.arn
    }
  }
}

resource "aws_s3_bucket_public_access_block" "data" {
  bucket = aws_s3_bucket.data.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

## Version Pinning

```hcl
terraform {
  required_version = ">= 1.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Allow minor updates
    }
  }
}
```

**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

```hcl
provider "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`
- `.tfvars` files with sensitive data

**Always commit:**
- All `.tf` configuration files
- `.terraform.lock.hcl` (dependency lock file)

## Validation Tools

Run before committing:

```bash
terraform fmt -recursive
terraform validate
```

Additional tools:
- `tflint` - Linting and best practices
- `checkov` / `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](https://developer.hashicorp.com/terraform/language/style)*

Overview

This skill generates Terraform HCL that follows HashiCorp’s official style conventions and recommended best practices. It produces well-organized files, consistent formatting, secure defaults, and sensible naming so configurations are easy to review, reuse, and maintain. Use it when creating, reviewing, or automating Terraform code to ensure policy-aligned output.

How this skill works

The skill outputs Terraform code organized into standard files (terraform.tf, providers.tf, main.tf, variables.tf, outputs.tf, locals.tf) and follows a generation strategy: provider/version constraints first, then data sources, resources by dependency, variables for configurable values, and outputs for key attributes. It enforces formatting rules (two-space indentation, aligned equals), block ordering, naming conventions, and security hardening like encryption, private networking, and no hardcoded secrets.

When to use it

  • Scaffolding a new Terraform module or project
  • Automating infrastructure generation from templates or inputs
  • Reviewing or refactoring existing Terraform code to match style guidelines
  • Generating examples or documentation-ready HCL
  • Enforcing team-wide style and security standards during CI/CD pipelines

Best practices

  • Start with terraform and provider version constraints and pin providers appropriately
  • Organize files by responsibility: providers, variables (alphabetical), locals, main, outputs (alphabetical)
  • Use descriptive, lowercase_with_underscores names for resources and variables
  • Declare type and description for every variable; mark sensitive values with sensitive = true
  • Prefer for_each for multiple named resources and use count only for simple conditional creation
  • Run terraform fmt -recursive and terraform validate, and use tflint / checkov / tfsec for additional checks

Example use cases

  • Generate a new AWS VPC module with variables, locals, secure S3 bucket, and outputs
  • Convert an ad-hoc set of resources into a cleanly organized repository with pinned versions and provider blocks
  • Produce CI-ready Terraform artifacts that pass formatting and validation before merge
  • Create infrastructure examples for documentation that follow naming, formatting, and security rules
  • Automate multi-region provider configuration with aliased providers and clear defaults

FAQ

Does the skill hardcode credentials or secrets?

No. It never hardcodes credentials and marks sensitive outputs/variables with sensitive = true; secrets should be passed via secure mechanisms like environment variables or secret managers.

How does it choose between count and for_each?

It prefers for_each for creating multiple named resources to preserve identity and uses count only for simple conditional creation (0 or 1) per the style guide.