terraform · sanjeed5 — Cursor rule: what it does, how to install
Enforces opinionated Terraform guidelines in Cursor for project layout, resource naming, remote state locking, and modular design.
.cursor/rules/terraform.mdcWhat terraform does
This rule targets **/*.tf files and enforces strict environment isolation using dedicated directory trees rather than Terraform workspaces. It standardizes common project layouts around predictable files: backend.tf, main.tf, variables.tf, outputs.tf, providers.tf, and locals.tf. Every input variable and output definition must include an explicit description attribute.
Naming standards require lower_snake_case while prohibiting the resource type inside the resource label itself. Boolean variables must use positive phrasing ending in _enabled. When generating resources from maps, the rule strictly mandates for_each over count to maintain state stability, alongside dynamic blocks for nested configurations and remote backend locking.
What it helps with
- Organize environment configs into separate directories instead of workspaces
- Enforce lower_snake_case resource and variable naming conventions
- Configure remote state backends with required DynamoDB locking
- Convert map iterations from fragile count indices to for_each
Example prompts for the agent
- Create a reusable VPC module following the project conventions
- Add an aws_security_group resource using dynamic blocks for ingress
- Write backend.tf for the production environment with S3 state storage
How to install terraform
Manually: put the files in place
.cursor/rules/terraform.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/terraform.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/terraform.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/terraform.mdManually: put the files in place
.clinerules/terraform.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about terraform
How to install terraform in Cursor?
Place the rule file at .cursor/rules/terraform.mdc in your project repository.
What does the terraform rule do?
It guides the Cursor agent whenever handling **/*.tf files, demanding strict layout patterns, positive boolean flags, descriptive outputs, and remote backend locking.
Can I use terraform workspaces for environment separation with this rule?
No, the standard explicitly avoids workspaces for separating dev and prod, requiring separate environment directories with distinct remote backends instead.
Source text
terraform.mdc · CC0-1.0
---
description: Definitive guidelines for writing maintainable, secure, and scalable Terraform configurations. Focuses on structure, naming, state management, and automation.
globs: **/*.tf
---
# Terraform Best Practices
Terraform configurations are production-grade software. Treat them with the same rigor: readable, versioned, testable, and governed by automated policies. This guide provides opinionated, actionable best practices for our team's Terraform development, ensuring consistency, reliability, and security.
## 1. Code Organization and Structure
Organize your Terraform code for clarity, reusability, and environment isolation.
### 1.1 Project Layout
**Always** separate environments and reusable components.
❌ BAD: Monolithic `main.tf` or environment-specific resources mixed with modules.
```terraform
# environments/dev/main.tf
resource "aws_vpc" "dev_vpc" { # ... }
resource "aws_s3_bucket" "dev_app_bucket" { # ... }
# Duplicated for staging/prod
```
✅ GOOD: Dedicated directories for environments and shared modules.
```
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── backend.tf
│ │ └── terraform.tfvars
│ ├── prod/
│ ├── main.tf
│ ├── variables.tf
│ ├── backend.tf
│ └── terraform.tfvars
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── s3_bucket/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
```
### 1.2 Standard File Naming
Use HashiCorp's recommended file naming conventions for consistency.
* `backend.tf`: Remote state configuration.
* `main.tf`: Primary resource and module calls.
* `outputs.tf`: All output blocks, alphabetically sorted.
* `providers.tf`: `terraform` block (required version/providers) and `provider` blocks.
* `variables.tf`: All variable blocks, alphabetically sorted.
* `locals.tf`: Local values.
## 2. Naming Conventions
Consistency in naming is paramount for readability.
### 2.1 Resource and Variable Names
Use `lower_snake_case` for all resource, data source, variable, and output names. **Never** include the resource type in the name.
❌ BAD:
```terraform
resource "aws_s3_bucket" "s3_bucket_app_logs" { # ... }
variable "vpcName" { # ... }
```
✅ GOOD:
```terraform
resource "aws_s3_bucket" "app_logs" { # ... }
variable "vpc_name" { # ... }
```
### 2.2 Boolean Variables
Use positive boolean flags ending with `_enabled`.
❌ BAD:
```terraform
variable "disable_encryption" { type = bool }
```
✅ GOOD:
```terraform
variable "encryption_enabled" { type = bool }
```
### 2.3 Descriptions
**Every** `variable` and `output` block **must** include a `description`.
```terraform
variable "instance_type" {
description = "The EC2 instance type to use for the server."
type = string
default = "t3.micro"
}
output "vpc_id" {
description = "The ID of the created VPC."
value = aws_vpc.main.id
}
```
## 3. State Management
Remote state and locking are non-negotiable for team collaboration.
### 3.1 Remote State and Locking
**Always** use a remote backend (e.g., S3 with DynamoDB locking). **Never** rely on local state in shared environments.
```terraform
# backend.tf
terraform {
backend "s3" {
bucket = "my-team-terraform-state"
key = "environments/prod/app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock" # Must be pre-created
encrypt = true
}
}
```
### 3.2 Workspaces
**Avoid** `terraform workspace` for environment isolation. Use separate backend configurations in distinct directories (e.g., `environments/dev`, `environments/prod`) instead. Workspaces are primarily for ephemeral, short-lived deployments or testing variations within a single configuration.
## 4. Modules: Reusability and Abstraction
Modules are the cornerstone of scalable Terraform.
### 4.1 Module Design
Design modules to be single-purpose, composable, and reusable. Abstract implementation details.
❌ BAD: A module that creates a VPC, an RDS instance, and an S3 bucket.
✅ GOOD: Separate modules for `vpc`, `rds_instance`, and `s3_bucket`.
### 4.2 Module Inputs and Outputs
* **Inputs**: Parameterize only what needs to vary. Use `nullable = false` for variables that should never be `null` (especially lists, maps, objects defaulting to empty).
* **Outputs**: Provide meaningful outputs with descriptions. **Never** pass outputs directly through input variables; reference resource attributes to ensure implicit dependencies.
```terraform
# modules/vpc/variables.tf
variable "vpc_cidr" {
description = "CIDR block for the VPC."
type = string
}
variable "tags" {
description = "A map of tags to assign to all resources in the VPC."
type = map(string)
default = {}
nullable = false # Ensure tags is always a map, even if empty
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "The ID of the created VPC."
value = aws_vpc.main.id
}
```
## 5. Common Patterns and Anti-patterns
### 5.1 Loops and Conditionals (`for_each`, `count`)
Use `for_each` for creating multiple instances of a resource when you have a map or set of strings, as it provides better state management and readability than `count`. Use `count` for simple numeric iteration or conditional resource creation (when `count` is 0 or 1).
❌ BAD: Using `count` with a map for resource creation.
```terraform
resource "aws_instance" "app_servers" {
count = length(var.instance_configs) # var.instance_configs is a map
# ...
tags = {
Name = var.instance_configs[count.index].name
}
}
```
✅ GOOD: Using `for_each` with a map for resource creation.
```terraform
resource "aws_instance" "app_servers" {
for_each = var.instance_configs # var.instance_configs is a map of objects
# ...
tags = {
Name = each.value.name
}
}
```
### 5.2 Dynamic Blocks
Use dynamic blocks to construct repeatable nested blocks based on a complex variable.
```terraform
resource "aws_security_group" "web" {
# ...
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
```
### 5.3 Tagging Resources
**Always** tag all resources with meaningful metadata (e.g., `Project`, `Environment`, `Owner`, `CostCenter`). Use a `default_tags` block in your provider configuration.
```terraform
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
Project = "MyApplication"
Environment = "prod"
ManagedBy = "Terraform"
}
}
}
```
## 6. Security and Configuration Management
### 6.1 Secrets Management
**Never** hardcode sensitive data. Fetch secrets dynamically from a dedicated secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault).
❌ BAD:
```terraform
resource "aws_db_instance" "app_db" {
password = "SuperSecretPassword123!" # NEVER DO THIS
}
```
✅ GOOD:
```terraform
data "aws_secretsmanager_secret_version" "db_creds" {
secret_id = "prod/my-app/db-credentials"
}
locals {
db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_creds.secret_string)
}
resource "aws_db_instance" "app_db" {
password = local.db_credentials.password
}
```
### 6.2 Policy as Code
Integrate Policy as Code (e.g., Open Policy Agent (OPA) or HashiCorp Sentinel) into your CI/CD pipeline to enforce organizational compliance and security policies before deployment.
## 7. Common Pitfalls and Gotchas
### 7.1 Formatting and Validation
**Always** run `terraform fmt` and `terraform validate` before committing. Integrate these into Git pre-commit hooks and CI/CD pipelines.
### 7.2 Linting
Use a linter like TFLint to catch style violations, potential errors, and enforce best practices early.
### 7.3 Version Pinning
**Always** pin versions for Terraform, providers, and modules to ensure reproducible deployments and prevent unexpected changes.
```terraform
# providers.tf
terraform {
required_version = "~> 1.5" # Pin Terraform CLI version
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Pin AWS provider version
}
}
}
# main.tf (for modules)
module "vpc" {
source = "cloudposse/vpc/aws"
version = "2.0.0" # Pin module version
# ...
}
```
### 7.4 Lifecycle Management
Use the `lifecycle` block judiciously, especially `prevent_destroy` for critical resources and `create_before_destroy` for zero-downtime updates.
```terraform
resource "aws_s3_bucket" "critical_data" {
bucket = "my-critical-data-bucket"
# ...
lifecycle {
prevent_destroy = true # Protect critical resources from accidental deletion
}
}
```
## 8. CI/CD Integration
Automate your Terraform workflow with CI/CD.
### 8.1 Automated Plan Checks
**Every** pull request **must** trigger a `terraform plan` and post the output as a comment or check. This ensures visibility and prevents unexpected changes.
### 8.2 Automated Apply
Implement automated `terraform apply` on merge to `main` (or environment branches) only after successful plan, validation, and policy checks. This requires robust state locking and access controls.
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc
Similar IDE rules
All IDE rules for DevOps and infrastructure →terraform by other authors comes first.