Terraform Workflow Rules
1. Terraform Code Analysis Rules
Code-First Analysis Principle
- ALWAYS read Terraform code before making assumptions about infrastructure state
- NEVER assume missing resources need manual creation without verifying Terraform code
- Start analysis with
modules/ and environments/ directory structure
- When encountering unclear or ambiguous aspects of Terraform or cloud provider resources, consult official documentation to verify behavior, command syntax, and resource attributes
Resource Creation vs Reference
resource blocks CREATE new resources (Terraform manages these)
data blocks REFERENCE existing resources (must exist before Terraform run)
- Proxy subnets, firewall rules, and compute resources are typically CREATED by Terraform modules
Declarative Nature Understanding
- Terraform is DECLARATIVE: it defines WHAT should exist, not HOW to create it
- Missing resources in target environment likely means Terraform hasn't been run yet, not that they need manual creation
- Pre-creating resources that Terraform will create causes resource conflicts and errors
Validation Workflow
- Read module code to understand what resources are created
- Check variable flow from environments to modules
- Verify if resources are created (
resource) or referenced (data)
- Only then assess current infrastructure state with CLI tools
- Never skip step 1-3 even if infrastructure state seems obvious
Common Anti-Patterns
- ❌ "I see missing subnet, so I must create it manually first"
- ❌ "Firewall rules don't exist, so they're prerequisites"
- ✅ "Let me check if the module creates these resources automatically"
- ✅ "What does the Terraform code actually do?"
Variable Wiring Verification
- When adding a new variable to an environment's
variables.tf, always verify the complete wiring chain:
terraform.tfvars (actual value) → variables.tf (variable declaration) → main.tf (module call parameter) → modules/*/variables.tf (module variable)
- Missing any link in this chain causes the variable to silently use its default value instead of the intended value
- A variable defined in
variables.tf but NOT passed in the module {} block of main.tf will be ignored — the module will use its own default
- After adding or modifying variables, run
terraform plan to confirm the expected values are applied
- When wiring a variable across multiple environments (dev, stage, prod), verify all environments individually — do not assume one environment's wiring is replicated in others
2. Plan Output Interpretation
Symbol Reference
| Symbol |
Meaning |
Risk Level |
+ |
Resource will be created |
Low |
~ |
Resource will be updated in-place |
Medium |
- |
Resource will be destroyed |
High |
-/+ |
Resource will be destroyed and recreated |
High |
<= |
Data source will be read |
Low |
Review Guidelines
- Always review the full plan output before applying
- Pay special attention to
- and -/+ changes — these indicate potential downtime or data loss
- When
-/+ appears, check if the triggering attribute change is intentional (e.g., name change forces replacement in many resources)
- Verify the total count of changes matches expectations:
Plan: X to add, Y to change, Z to destroy
3. Resource Design Best Practices
count vs for_each Selection
- Prefer
for_each over count for resource creation
count identifies resources by index — adding/removing items shifts all subsequent indices, causing unnecessary destroy/recreate
for_each identifies resources by key — adding/removing items only affects the specific resource
- Use
count only for simple conditional creation (count = var.enabled ? 1 : 0)
depends_on Usage
- Prefer implicit dependencies (resource references) over explicit
depends_on
depends_on should be a last resort — it creates a hard dependency that forces serial execution
- Common valid use case: when a resource depends on a side effect (e.g., IAM policy must exist before a resource can use the role, but there is no direct attribute reference)
lifecycle Block
prevent_destroy: Use for critical resources (databases, storage) that should never be accidentally deleted
create_before_destroy: Use when replacement must avoid downtime (e.g., SSL certificates, load balancer backends)
ignore_changes: Use for attributes managed outside Terraform (e.g., auto-scaling group size, tags managed by external systems)
- Avoid overusing
ignore_changes — it hides drift and can mask real configuration issues
4. Module Change Impact Rules
Scope Awareness
- Module changes (
modules/) affect ALL environments that reference the module
- Before modifying a module, verify impact across all environments
- Environment-specific changes should go in
environments/{env}/ only
Verification Checklist
- Identify which environments use the modified module
- Check if variable defaults or required inputs changed
- Run
terraform plan in each affected environment
- Review plan output for unintended resource changes (especially destroy/recreate)
5. Terraform Workflow Rules
Code Quality
- Run
terraform fmt after every code change
- Run
terraform validate before committing
State Management
- State files (
.tfstate, .tfstate.backup, .tfstate.*.backup) must NEVER be committed to version control
- When resources are created outside Terraform or need to be removed from state, use
terraform state rm and terraform import commands
- Always run
terraform init when switching between service directories or after modifying provider/backend configurations
- For team collaboration, use remote backend (e.g., GCS, S3, Terraform Cloud) with state locking enabled to prevent concurrent modifications
Apply Failure Recovery
- When
terraform apply fails mid-way (e.g., resource already exists, timeout, quota exceeded):
- Identify which resources were successfully created and which failed
- For resources that exist in the cloud but not in Terraform state: use
terraform import to bring them under management
- For resources in Terraform state that were not actually created: use
terraform state rm to remove the stale reference
- For orphaned resources (created but not needed): delete via provider CLI, then clean state with
terraform state rm
- After recovery, always run
terraform plan to verify the state is consistent before attempting terraform apply again
- Common failure pattern: resource already exists error (e.g., GCP
409 alreadyExists, AWS AlreadyExistsException) — resource was previously created manually or by a prior failed apply. Resolve by importing the existing resource or deleting it and re-applying
Production Safety Protocol
- ALWAYS verify changes in dev/stage environments before applying to prod
- NEVER assume prod configuration is identical to dev/stage — verify variables and values explicitly
- Use
terraform plan with detailed output review before any prod apply
- Enable deletion protection for critical prod resources (databases, storage, networking)
- Maintain separate state files and backend configurations for each environment
- For prod environments, add explicit confirmation steps before destructive operations
6. Deprecated Commands and Modern Alternatives
terraform taint → terraform apply -replace
terraform taint is deprecated since v0.15.2
- Use
terraform apply -replace="<resource_address>" instead
-replace is safer because it shows the full plan before execution, whereas taint creates a race condition where other team members could generate plans against the tainted resource before review
- ❌
terraform taint aws_instance.example
- ✅
terraform apply -replace="aws_instance.example"
terraform state mv → moved block
- For resource refactoring (renaming, moving into/out of modules), prefer the
moved block (v1.1+) over terraform state mv
moved block is tracked in version control and applied automatically during terraform apply
terraform state mv is a one-off CLI operation with no audit trail in code
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}
```hcl
### `terraform import` CLI → `import` block
- For importing existing resources, prefer the `import` block (v1.5+) over `terraform import` CLI
- `import` block integrates into the standard `terraform apply` workflow and is tracked in code
- Use `terraform plan -generate-config-out=generated.tf` to auto-generate the corresponding `resource` block for the imported resource
- `terraform import` CLI is a one-off operation that modifies state directly without plan review
```hcl
import {
to = aws_instance.example
id = "i-1234567890abcdef0"
}
```hcl
### Provisioners (`provisioner` block)
- Provisioners (`local-exec`, `remote-exec`, `file`) are **discouraged** by HashiCorp
- Use cloud-init, Packer, or configuration management tools (Ansible, etc.) instead
- If unavoidable, treat provisioners as a last resort and document the reason
---
## 7. Version Support and Features (2026-03-14)
### Current Versions
| Version | Status | Latest Patch | Notes |
| --- | --- | --- | --- |
| 1.14 | Supported | 1.14.7 | Current stable |
| 1.13 | Supported | 1.13.5 | |
| 1.12 | EOL | 1.12.2 | Ended 2025-11-19 |
| 1.11 | EOL | 1.11.4 | Ended 2025-08-20 |
| 1.10 | EOL | 1.10.5 | Ended 2025-05-14 |
### Terraform 1.15 (Upcoming)
Terraform 1.15 (currently alpha) introduces significant new features:
#### New Features
- **Windows ARM64 support**: Native builds for Windows on ARM
- **`deprecated` attribute**: Mark variables and outputs as deprecated
```hcl
variable "old_variable" {
type = string
deprecated = "Use new_variable instead"
}
output "legacy_output" {
value = module.example.result
deprecated = "This output will be removed in v2.0"
}
convert function: Precise inline type conversions
variable "config" {
type = convert(var.raw_config, object({
name = string
tags = list(string)
}))
}
Variables in module source: Dynamic module sources
module "app" {
source = "${var.module_registry}/${var.module_name}"
version = var.module_version
}
Backend validation: terraform validate now checks backend blocks
S3 backend: Support for aws login authentication
Migration Planning
- Review deprecation warnings in current code
- Plan variable/output migrations before 1.15 upgrade
- Test
convert function for complex type scenarios
8. Provider Version Considerations
AWS Provider (v6.x)
- Latest: v6.36.0 (2026-03-11)
- Requires Terraform >= 1.0
- Major version upgrades require explicit
version = "~> 6.0" in required_providers
Azure Provider (azurerm v4.x)
- Latest: v4.x series
- Breaking changes from v3.x: resource renames, property changes
- Review upgrade guide before major version upgrade
GCP Provider (google v6.x)
- Latest: v6.x series
- Beta provider (
google-beta) tracks main provider versioning
Provider Version Pinning
terraform {
required_version = "~> 1.14"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
```hcl
---
## 9. Testing and Validation
### Pre-Commit Hooks
Recommended `.pre-commit-config.yaml` for Terraform:
```yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.96.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_tfsec
```hcl
### Validation Commands
```bash
# Format check
terraform fmt -check -recursive
# Validate configuration
terraform validate
# Security scan (tfsec)
tfsec .
# Lint (tflint)
tflint --init && tflint
```hcl
### CI/CD Pipeline Stages
1. `terraform fmt -check` — Code style
2. `terraform validate` — Syntax and schema
3. `tflint` — Best practices
4. `tfsec` — Security analysis
5. `terraform plan` — Preview changes
6. Manual approval (for prod)
7. `terraform apply` — Apply changes
---
## 10. Provider-Specific Skills
For provider-specific issues and best practices, see dedicated skills:
- **AWS Provider**: `terraform-aws-provider` skill
- **Azure Provider**: `terraform-azure-provider` skill
- **GCP Provider**: `terraform-gcp-provider` skill
## Additional References
- For AWS provider v6.x issues and best practices, see [references/aws-provider.md](references/aws-provider.md)
- For Azure provider v4.x issues and best practices, see [references/azure-provider.md](references/azure-provider.md)
- For GCP provider v6.x issues and best practices, see [references/gcp-provider.md](references/gcp-provider.md)
1---2name: terraform-workflow3description: Terraform core workflow rules, state management, module design patterns, and version considerations. Includes provider-specific guides for AWS (v6.x), Azure (v4.x), and GCP (v6.x). Use for Terraform operations and best practices.4license: MIT5---6# Terraform Workflow Rules78## 1. Terraform Code Analysis Rules910### Code-First Analysis Principle1112- ALWAYS read Terraform code before making assumptions about infrastructure state13- NEVER assume missing resources need manual creation without verifying Terraform code14- Start analysis with `modules/` and `environments/` directory structure15- When encountering unclear or ambiguous aspects of Terraform or cloud provider resources, consult official documentation to verify behavior, command syntax, and resource attributes1617### Resource Creation vs Reference1819- `resource` blocks CREATE new resources (Terraform manages these)20- `data` blocks REFERENCE existing resources (must exist before Terraform run)21- Proxy subnets, firewall rules, and compute resources are typically CREATED by Terraform modules2223### Declarative Nature Understanding2425- Terraform is DECLARATIVE: it defines WHAT should exist, not HOW to create it26- Missing resources in target environment likely means Terraform hasn't been run yet, not that they need manual creation27- Pre-creating resources that Terraform will create causes resource conflicts and errors2829### Validation Workflow30311. Read module code to understand what resources are created322. Check variable flow from environments to modules333. Verify if resources are created (`resource`) or referenced (`data`)344. Only then assess current infrastructure state with CLI tools355. Never skip step 1-3 even if infrastructure state seems obvious3637### Common Anti-Patterns3839- ❌ "I see missing subnet, so I must create it manually first"40- ❌ "Firewall rules don't exist, so they're prerequisites"41- ✅ "Let me check if the module creates these resources automatically"42- ✅ "What does the Terraform code actually do?"4344### Variable Wiring Verification4546- When adding a new variable to an environment's `variables.tf`, always verify the complete wiring chain:47 1. `terraform.tfvars` (actual value) → `variables.tf` (variable declaration) → `main.tf` (module call parameter) → `modules/*/variables.tf` (module variable)48 2. Missing any link in this chain causes the variable to silently use its default value instead of the intended value49- A variable defined in `variables.tf` but NOT passed in the `module {}` block of `main.tf` will be ignored — the module will use its own default50- After adding or modifying variables, run `terraform plan` to confirm the expected values are applied51- When wiring a variable across multiple environments (dev, stage, prod), verify all environments individually — do not assume one environment's wiring is replicated in others5253---5455## 2. Plan Output Interpretation5657### Symbol Reference5859| Symbol | Meaning | Risk Level |60| --- | --- | --- |61| `+` | Resource will be created | Low |62| `~` | Resource will be updated in-place | Medium |63| `-` | Resource will be destroyed | High |64| `-/+` | Resource will be destroyed and recreated | High |65| `<=` | Data source will be read | Low |6667### Review Guidelines6869- Always review the full plan output before applying70- Pay special attention to `-` and `-/+` changes — these indicate potential downtime or data loss71- When `-/+` appears, check if the triggering attribute change is intentional (e.g., `name` change forces replacement in many resources)72- Verify the total count of changes matches expectations: `Plan: X to add, Y to change, Z to destroy`7374---7576## 3. Resource Design Best Practices7778### `count` vs `for_each` Selection7980- Prefer `for_each` over `count` for resource creation81- `count` identifies resources by index — adding/removing items shifts all subsequent indices, causing unnecessary destroy/recreate82- `for_each` identifies resources by key — adding/removing items only affects the specific resource83- Use `count` only for simple conditional creation (`count = var.enabled ? 1 : 0`)8485### `depends_on` Usage8687- Prefer implicit dependencies (resource references) over explicit `depends_on`88- `depends_on` should be a last resort — it creates a hard dependency that forces serial execution89- Common valid use case: when a resource depends on a side effect (e.g., IAM policy must exist before a resource can use the role, but there is no direct attribute reference)9091### `lifecycle` Block9293- `prevent_destroy`: Use for critical resources (databases, storage) that should never be accidentally deleted94- `create_before_destroy`: Use when replacement must avoid downtime (e.g., SSL certificates, load balancer backends)95- `ignore_changes`: Use for attributes managed outside Terraform (e.g., auto-scaling group size, tags managed by external systems)96- Avoid overusing `ignore_changes` — it hides drift and can mask real configuration issues9798---99100## 4. Module Change Impact Rules101102### Scope Awareness103104- Module changes (`modules/`) affect ALL environments that reference the module105- Before modifying a module, verify impact across all environments106- Environment-specific changes should go in `environments/{env}/` only107108### Verification Checklist1091101. Identify which environments use the modified module1112. Check if variable defaults or required inputs changed1123. Run `terraform plan` in each affected environment1134. Review plan output for unintended resource changes (especially destroy/recreate)114115---116117## 5. Terraform Workflow Rules118119### Code Quality120121- Run `terraform fmt` after every code change122- Run `terraform validate` before committing123124### State Management125126- State files (`.tfstate`, `.tfstate.backup`, `.tfstate.*.backup`) must NEVER be committed to version control127- When resources are created outside Terraform or need to be removed from state, use `terraform state rm` and `terraform import` commands128- Always run `terraform init` when switching between service directories or after modifying provider/backend configurations129- For team collaboration, use remote backend (e.g., GCS, S3, Terraform Cloud) with state locking enabled to prevent concurrent modifications130131### Apply Failure Recovery132133- When `terraform apply` fails mid-way (e.g., resource already exists, timeout, quota exceeded):134 1. Identify which resources were successfully created and which failed135 2. For resources that exist in the cloud but not in Terraform state: use `terraform import` to bring them under management136 3. For resources in Terraform state that were not actually created: use `terraform state rm` to remove the stale reference137 4. For orphaned resources (created but not needed): delete via provider CLI, then clean state with `terraform state rm`138- After recovery, always run `terraform plan` to verify the state is consistent before attempting `terraform apply` again139- Common failure pattern: resource already exists error (e.g., GCP `409 alreadyExists`, AWS `AlreadyExistsException`) — resource was previously created manually or by a prior failed apply. Resolve by importing the existing resource or deleting it and re-applying140141### Production Safety Protocol142143- ALWAYS verify changes in dev/stage environments before applying to prod144- NEVER assume prod configuration is identical to dev/stage — verify variables and values explicitly145- Use `terraform plan` with detailed output review before any prod apply146- Enable deletion protection for critical prod resources (databases, storage, networking)147- Maintain separate state files and backend configurations for each environment148- For prod environments, add explicit confirmation steps before destructive operations149150---151152## 6. Deprecated Commands and Modern Alternatives153154### `terraform taint` → `terraform apply -replace`155156- `terraform taint` is **deprecated** since v0.15.2157- Use `terraform apply -replace="<resource_address>"` instead158- `-replace` is safer because it shows the full plan before execution, whereas `taint` creates a race condition where other team members could generate plans against the tainted resource before review159- ❌ `terraform taint aws_instance.example`160- ✅ `terraform apply -replace="aws_instance.example"`161162### `terraform state mv` → `moved` block163164- For resource refactoring (renaming, moving into/out of modules), prefer the `moved` block (v1.1+) over `terraform state mv`165- `moved` block is tracked in version control and applied automatically during `terraform apply`166- `terraform state mv` is a one-off CLI operation with no audit trail in code167168```hcl169moved {170 from = aws_instance.old_name171 to = aws_instance.new_name172}173```hcl174175### `terraform import` CLI → `import` block176177- For importing existing resources, prefer the `import` block (v1.5+) over `terraform import` CLI178- `import` block integrates into the standard `terraform apply` workflow and is tracked in code179- Use `terraform plan -generate-config-out=generated.tf` to auto-generate the corresponding `resource` block for the imported resource180- `terraform import` CLI is a one-off operation that modifies state directly without plan review181182```hcl183import {184 to = aws_instance.example185 id = "i-1234567890abcdef0"186}187```hcl188189### Provisioners (`provisioner` block)190191- Provisioners (`local-exec`, `remote-exec`, `file`) are **discouraged** by HashiCorp192- Use cloud-init, Packer, or configuration management tools (Ansible, etc.) instead193- If unavoidable, treat provisioners as a last resort and document the reason194195---196197## 7. Version Support and Features (2026-03-14)198199### Current Versions200201| Version | Status | Latest Patch | Notes |202| --- | --- | --- | --- |203| 1.14 | Supported | 1.14.7 | Current stable |204| 1.13 | Supported | 1.13.5 | |205| 1.12 | EOL | 1.12.2 | Ended 2025-11-19 |206| 1.11 | EOL | 1.11.4 | Ended 2025-08-20 |207| 1.10 | EOL | 1.10.5 | Ended 2025-05-14 |208209### Terraform 1.15 (Upcoming)210211Terraform 1.15 (currently alpha) introduces significant new features:212213#### New Features214215- **Windows ARM64 support**: Native builds for Windows on ARM216- **`deprecated` attribute**: Mark variables and outputs as deprecated217218 ```hcl219 variable "old_variable" {220 type = string221 deprecated = "Use new_variable instead"222 }223224 output "legacy_output" {225 value = module.example.result226 deprecated = "This output will be removed in v2.0"227 }228 ```229230- **`convert` function**: Precise inline type conversions231232 ```hcl233 variable "config" {234 type = convert(var.raw_config, object({235 name = string236 tags = list(string)237 }))238 }239 ```240241- **Variables in module source**: Dynamic module sources242243 ```hcl244 module "app" {245 source = "${var.module_registry}/${var.module_name}"246 version = var.module_version247 }248 ```249250- **Backend validation**: `terraform validate` now checks backend blocks251- **S3 backend**: Support for `aws login` authentication252253#### Migration Planning254255- Review deprecation warnings in current code256- Plan variable/output migrations before 1.15 upgrade257- Test `convert` function for complex type scenarios258259---260261## 8. Provider Version Considerations262263### AWS Provider (v6.x)264265- Latest: v6.36.0 (2026-03-11)266- Requires Terraform >= 1.0267- Major version upgrades require explicit `version = "~> 6.0"` in `required_providers`268269### Azure Provider (azurerm v4.x)270271- Latest: v4.x series272- Breaking changes from v3.x: resource renames, property changes273- Review [upgrade guide](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/4.0-upgrade-guide) before major version upgrade274275### GCP Provider (google v6.x)276277- Latest: v6.x series278- Beta provider (`google-beta`) tracks main provider versioning279280### Provider Version Pinning281282```hcl283terraform {284 required_version = "~> 1.14"285286 required_providers {287 aws = {288 source = "hashicorp/aws"289 version = "~> 6.0"290 }291 azurerm = {292 source = "hashicorp/azurerm"293 version = "~> 4.0"294 }295 }296}297```hcl298299---300301## 9. Testing and Validation302303### Pre-Commit Hooks304305Recommended `.pre-commit-config.yaml` for Terraform:306307```yaml308repos:309 - repo: https://github.com/antonbabenko/pre-commit-terraform310 rev: v1.96.0311 hooks:312 - id: terraform_fmt313 - id: terraform_validate314 - id: terraform_tflint315 - id: terraform_tfsec316```hcl317318### Validation Commands319320```bash321# Format check322terraform fmt -check -recursive323324# Validate configuration325terraform validate326327# Security scan (tfsec)328tfsec .329330# Lint (tflint)331tflint --init && tflint332```hcl333334### CI/CD Pipeline Stages3353361. `terraform fmt -check` — Code style3372. `terraform validate` — Syntax and schema3383. `tflint` — Best practices3394. `tfsec` — Security analysis3405. `terraform plan` — Preview changes3416. Manual approval (for prod)3427. `terraform apply` — Apply changes343344---345346## 10. Provider-Specific Skills347348For provider-specific issues and best practices, see dedicated skills:349350- **AWS Provider**: `terraform-aws-provider` skill351- **Azure Provider**: `terraform-azure-provider` skill352- **GCP Provider**: `terraform-gcp-provider` skill353354## Additional References355356- For AWS provider v6.x issues and best practices, see [references/aws-provider.md](references/aws-provider.md)357- For Azure provider v4.x issues and best practices, see [references/azure-provider.md](references/azure-provider.md)358- For GCP provider v6.x issues and best practices, see [references/gcp-provider.md](references/gcp-provider.md)