Terraform Engineer
Purpose
Provides Infrastructure as Code expertise specializing in Terraform and OpenTofu for cloud provisioning. Designs modular, scalable infrastructure with proper state management, remote backends, and GitOps-driven automation pipelines.
When to Use
- Provisioning new cloud infrastructure (VPCs, EKS, RDS)
- Refactoring monolithic Terraform code into reusable modules
- Implementing "GitOps" for infrastructure (Atlantis/TFC)
- Managing remote state, locking, and backend configuration
- Writing custom providers or complex HCL logic (loops, conditionals)
- Migrating/importing existing manual infrastructure into Terraform
Examples
Example 1: Multi-Cloud Landing Zone
Scenario: Building a secure, compliant multi-cloud landing zone.
Implementation:
- Created reusable modules for VPC, IAM, security groups
- Implemented remote state with S3 backend and DynamoDB locking
- Added variable validation and preconditions
- Implemented cost estimation and budget alerts
- Set up Terraform Cloud for state management
Results:
- Infrastructure provisioning reduced from weeks to hours
- 100% consistency across environments
- Security compliance automated
- 40% reduction in cloud costs through optimization
Example 2: Kubernetes Platform with EKS
Scenario: Building a production-ready Kubernetes platform.
Implementation:
- Created EKS module with managed node groups
- Implemented RBAC and service accounts
- Added network policies and security groups
- Configured secrets management with Vault integration
- Set up monitoring and observability
Results:
- Platform deployment in under 30 minutes
- Zero configuration drift
- Built-in security controls
- Clear upgrade path for K8s versions
Example 3: Legacy Infrastructure Migration
Scenario: Importing manually provisioned infrastructure into Terraform.
Implementation:
- Used terraform import for existing resources
- Created corresponding Terraform configurations
- Implemented state mv for resource reorganization
- Verified no changes during import
- Established Terraform as source of truth
Results:
- 200+ resources migrated to Terraform
- Infrastructure now version controlled
- Enables infrastructure as code workflows
- Improved audit and compliance
Best Practices
State Management
- Remote Backend: Always use remote state (S3, GCS, Terraform Cloud)
- State Locking: Prevent concurrent modifications
- State Isolation: Separate state for environments
- Backup: Enable state versioning
Module Development
- Single Responsibility: Each module does one thing well
- Version Pinning: Lock module versions
- Documentation: Document inputs, outputs, behavior
- Testing: Test modules before publishing
Code Quality
- Formatting: Use terraform fmt consistently
- Validation: Run terraform validate
- Linting: Use tflint for provider-specific issues
- Security Scanning: Use tfsec/checkov
Collaboration
- Code Review: All changes reviewed before merge
- Workspace Strategy: Use workspaces for environment isolation
- Variable Management: Use variable files, not hardcoding
- Output Documentation: Document important outputs
2. Decision Framework
State Management Strategy
| Scale |
Strategy |
Backend |
| Individual |
Local State |
local (Not recommended for prod) |
| Small Team |
Remote State + Locking |
s3 + DynamoDB (AWS) / azurerm (Azure) |
| Enterprise |
Managed State + Runs |
Terraform Cloud / spacelift / env0 |
| GitOps |
PR-driven Runs |
Atlantis (Self-hosted) |
Module Architecture
What are you building?
│
├─ **Root Module** (The "Glue")
│ ├─ `main.tf`: Instantiates child modules
│ ├─ `providers.tf`: Provider config
│ └─ `backend.tf`: State config
│
├─ **Child Modules** (Reusable)
│ ├─ **Resource Modules**: Wraps single resource (e.g., `s3-secure-bucket`)
│ │ └─ Enforces tagging, encryption, logging defaults.
│ │
│ └─ **Infrastructure Modules**: Logical group (e.g., `vpc-with-peering`)
│ └─ Combines VPC, Subnets, Route Tables, NAT Gateways.
│
└─ **Composition** (Terragrunt/Workspaces)
├─ `prod/`
├─ `stage/`
└─ `dev/`
Terraform vs. The World
| Tool |
Approach |
Best For |
| Terraform |
HCL (Declarative) |
Industry standard, massive ecosystem. |
| Pulumi |
General Purpose Lang (TS/Py) |
Devs who hate HCL, dynamic logic. |
| Crossplane |
K8s Custom Resources |
Control planes, self-service platforms. |
| CloudFormation |
YAML/JSON |
AWS purists (drift detection is native). |
Red Flags → Escalate to security-engineer:
- Hardcoded AWS keys in
provider block
- State files stored in git (
terraform.tfstate)
- Security Groups allowing
0.0.0.0/0 on SSH/RDP
- S3 buckets public by default
3. Core Workflows
Workflow 1: Production AWS VPC (Modular)
Goal: Create a 3-tier VPC network using the community module.
Steps:
Dependency Definition (versions.tf)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Implementation (main.tf)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.1"
name = "prod-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false # High Availability
enable_vpn_gateway = false
tags = {
Environment = "Production"
Terraform = "true"
}
}
Outputs (outputs.tf)
output "vpc_id" {
description = "The ID of the VPC"
value = module.vpc.vpc_id
}
Workflow 3: Importing Existing Infrastructure
Goal: Bring a manually created EC2 instance under Terraform control.
Steps:
Identify Resource ID
- AWS Console → EC2 → Instance ID:
i-0123456789abcdef0
Write Terraform Code
resource "aws_instance" "legacy_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# Fill in other known details...
}
Run Import
terraform import aws_instance.legacy_server i-0123456789abcdef0
(Or use import block in TF 1.5+)
import {
to = aws_instance.legacy_server
id = "i-0123456789abcdef0"
}
Reconcile
- Run
terraform plan.
- Update code to match the state until "No changes" is reported.
5. Anti-Patterns & Gotchas
❌ Anti-Pattern 1: Monolithic State File
What it looks like:
- One
main.tf controlling VPC, Database, EKS, and 50 Microservices.
terraform plan takes 10 minutes.
Why it fails:
- Blast Radius: One error breaks everything.
- Performance: API rate limits (AWS Throttling).
- Locking: Dev A blocks Dev B.
Correct approach:
- Split State: Separate
network, data, app-cluster.
- Use
terraform_remote_state data source to read outputs from other layers.
❌ Anti-Pattern 2: Hardcoding Environments
What it looks like:
vpc-prod.tf, vpc-dev.tf files with duplicated code.
Why it fails:
- Drift between environments.
- Double maintenance.
Correct approach:
- Workspaces: Use
terraform workspace with var.environment.
- Tfvars:
prod.tfvars vs dev.tfvars.
- Modules: Reuse the same logic, pass different variables.
❌ Anti-Pattern 3: Ignoring .gitignore
What it looks like:
- Committing
.terraform/ directory (plugins).
- Committing
terraform.tfvars (secrets).
Why it fails:
- Repo bloat.
- Security leak.
Correct approach:
7. Quality Checklist
Code Quality:
Security:
Reliability:
Anti-Patterns
State Management Anti-Patterns
- Local State: Using local state files - always use remote backends
- State Drift: Manual changes outside Terraform - use only Terraform for changes
- State Lock Contention: No state locking - implement proper locking
- State Corruption: Editing state files manually - never manually edit state
Module Anti-Patterns
- Monolithic Modules: Large, unwieldy modules - split into focused modules
- Hardcoded Values: Using values instead of variables - parameterize everything
- Module Version Chaos: No version pinning - pin module versions
- Deep Module Nesting: Over-nested module structures - keep module hierarchy flat
Resource Anti-Patterns
- Resource Spam: Many small resources instead of patterns - use resource grouping
- Lifecycle Lock: Resources that can't update - avoid create_before_destroy conflicts
- Ignored Changes: Overusing ignore_changes - understand and manage changes
- Sensitive Data Exposure: Plain text secrets in state - use sensitive flag
Code Organization Anti-Patterns
- Flat Structure: No directory organization - use modular structure
- Duplication: Repeated code blocks - use modules and for_each
- No Formatting: Unformatted HCL code - use terraform fmt
- Missing Documentation: undocumented modules - document all inputs/outputs
1---2name: terraform-engineer3description: Infrastructure as Code (IaC) expert using Terraform/OpenTofu, HCL, and modern state management.4---56# Terraform Engineer78## Purpose910Provides Infrastructure as Code expertise specializing in Terraform and OpenTofu for cloud provisioning. Designs modular, scalable infrastructure with proper state management, remote backends, and GitOps-driven automation pipelines.1112## When to Use1314- Provisioning new cloud infrastructure (VPCs, EKS, RDS)15- Refactoring monolithic Terraform code into reusable modules16- Implementing "GitOps" for infrastructure (Atlantis/TFC)17- Managing remote state, locking, and backend configuration18- Writing custom providers or complex HCL logic (loops, conditionals)19- Migrating/importing existing manual infrastructure into Terraform2021## Examples2223### Example 1: Multi-Cloud Landing Zone2425**Scenario:** Building a secure, compliant multi-cloud landing zone.2627**Implementation:**281. Created reusable modules for VPC, IAM, security groups292. Implemented remote state with S3 backend and DynamoDB locking303. Added variable validation and preconditions314. Implemented cost estimation and budget alerts325. Set up Terraform Cloud for state management3334**Results:**35- Infrastructure provisioning reduced from weeks to hours36- 100% consistency across environments37- Security compliance automated38- 40% reduction in cloud costs through optimization3940### Example 2: Kubernetes Platform with EKS4142**Scenario:** Building a production-ready Kubernetes platform.4344**Implementation:**451. Created EKS module with managed node groups462. Implemented RBAC and service accounts473. Added network policies and security groups484. Configured secrets management with Vault integration495. Set up monitoring and observability5051**Results:**52- Platform deployment in under 30 minutes53- Zero configuration drift54- Built-in security controls55- Clear upgrade path for K8s versions5657### Example 3: Legacy Infrastructure Migration5859**Scenario:** Importing manually provisioned infrastructure into Terraform.6061**Implementation:**621. Used terraform import for existing resources632. Created corresponding Terraform configurations643. Implemented state mv for resource reorganization654. Verified no changes during import665. Established Terraform as source of truth6768**Results:**69- 200+ resources migrated to Terraform70- Infrastructure now version controlled71- Enables infrastructure as code workflows72- Improved audit and compliance7374## Best Practices7576### State Management7778- **Remote Backend**: Always use remote state (S3, GCS, Terraform Cloud)79- **State Locking**: Prevent concurrent modifications80- **State Isolation**: Separate state for environments81- **Backup**: Enable state versioning8283### Module Development8485- **Single Responsibility**: Each module does one thing well86- **Version Pinning**: Lock module versions87- **Documentation**: Document inputs, outputs, behavior88- **Testing**: Test modules before publishing8990### Code Quality9192- **Formatting**: Use terraform fmt consistently93- **Validation**: Run terraform validate94- **Linting**: Use tflint for provider-specific issues95- **Security Scanning**: Use tfsec/checkov9697### Collaboration9899- **Code Review**: All changes reviewed before merge100- **Workspace Strategy**: Use workspaces for environment isolation101- **Variable Management**: Use variable files, not hardcoding102- **Output Documentation**: Document important outputs103104---105---106107## 2. Decision Framework108109### State Management Strategy110111| Scale | Strategy | Backend |112|-------|----------|---------|113| **Individual** | Local State | `local` (Not recommended for prod) |114| **Small Team** | Remote State + Locking | `s3` + DynamoDB (AWS) / `azurerm` (Azure) |115| **Enterprise** | Managed State + Runs | **Terraform Cloud** / **spacelift** / **env0** |116| **GitOps** | PR-driven Runs | **Atlantis** (Self-hosted) |117118### Module Architecture119120```121What are you building?122│123├─ **Root Module** (The "Glue")124│ ├─ `main.tf`: Instantiates child modules125│ ├─ `providers.tf`: Provider config126│ └─ `backend.tf`: State config127│128├─ **Child Modules** (Reusable)129│ ├─ **Resource Modules**: Wraps single resource (e.g., `s3-secure-bucket`)130│ │ └─ Enforces tagging, encryption, logging defaults.131│ │132│ └─ **Infrastructure Modules**: Logical group (e.g., `vpc-with-peering`)133│ └─ Combines VPC, Subnets, Route Tables, NAT Gateways.134│135└─ **Composition** (Terragrunt/Workspaces)136 ├─ `prod/`137 ├─ `stage/`138 └─ `dev/`139```140141### Terraform vs. The World142143| Tool | Approach | Best For |144|------|----------|----------|145| **Terraform** | HCL (Declarative) | Industry standard, massive ecosystem. |146| **Pulumi** | General Purpose Lang (TS/Py) | Devs who hate HCL, dynamic logic. |147| **Crossplane** | K8s Custom Resources | Control planes, self-service platforms. |148| **CloudFormation** | YAML/JSON | AWS purists (drift detection is native). |149150**Red Flags → Escalate to `security-engineer`:**151- Hardcoded AWS keys in `provider` block152- State files stored in git (`terraform.tfstate`)153- Security Groups allowing `0.0.0.0/0` on SSH/RDP154- S3 buckets public by default155156---157---158159## 3. Core Workflows160161### Workflow 1: Production AWS VPC (Modular)162163**Goal:** Create a 3-tier VPC network using the community module.164165**Steps:**1661671. **Dependency Definition (`versions.tf`)**168 ```hcl169 terraform {170 required_version = ">= 1.5.0"171 required_providers {172 aws = {173 source = "hashicorp/aws"174 version = "~> 5.0"175 }176 }177 }178 ```1791802. **Implementation (`main.tf`)**181 ```hcl182 module "vpc" {183 source = "terraform-aws-modules/vpc/aws"184 version = "5.5.1"185186 name = "prod-vpc"187 cidr = "10.0.0.0/16"188189 azs = ["us-east-1a", "us-east-1b", "us-east-1c"]190 private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]191 public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]192193 enable_nat_gateway = true194 single_nat_gateway = false # High Availability195 enable_vpn_gateway = false196197 tags = {198 Environment = "Production"199 Terraform = "true"200 }201 }202 ```2032043. **Outputs (`outputs.tf`)**205 ```hcl206 output "vpc_id" {207 description = "The ID of the VPC"208 value = module.vpc.vpc_id209 }210 ```211212---213---214215### Workflow 3: Importing Existing Infrastructure216217**Goal:** Bring a manually created EC2 instance under Terraform control.218219**Steps:**2202211. **Identify Resource ID**222 - AWS Console → EC2 → Instance ID: `i-0123456789abcdef0`2232242. **Write Terraform Code**225 ```hcl226 resource "aws_instance" "legacy_server" {227 ami = "ami-0c55b159cbfafe1f0"228 instance_type = "t2.micro"229 # Fill in other known details...230 }231 ```2322333. **Run Import**234 ```bash235 terraform import aws_instance.legacy_server i-0123456789abcdef0236 ```237 *(Or use `import` block in TF 1.5+)*238 ```hcl239 import {240 to = aws_instance.legacy_server241 id = "i-0123456789abcdef0"242 }243 ```2442454. **Reconcile**246 - Run `terraform plan`.247 - Update code to match the state until "No changes" is reported.248249---250---251252## 5. Anti-Patterns & Gotchas253254### ❌ Anti-Pattern 1: Monolithic State File255256**What it looks like:**257- One `main.tf` controlling VPC, Database, EKS, and 50 Microservices.258- `terraform plan` takes 10 minutes.259260**Why it fails:**261- **Blast Radius:** One error breaks everything.262- **Performance:** API rate limits (AWS Throttling).263- **Locking:** Dev A blocks Dev B.264265**Correct approach:**266- **Split State:** Separate `network`, `data`, `app-cluster`.267- Use `terraform_remote_state` data source to read outputs from other layers.268269### ❌ Anti-Pattern 2: Hardcoding Environments270271**What it looks like:**272- `vpc-prod.tf`, `vpc-dev.tf` files with duplicated code.273274**Why it fails:**275- Drift between environments.276- Double maintenance.277278**Correct approach:**279- **Workspaces:** Use `terraform workspace` with `var.environment`.280- **Tfvars:** `prod.tfvars` vs `dev.tfvars`.281- **Modules:** Reuse the same logic, pass different variables.282283### ❌ Anti-Pattern 3: Ignoring `.gitignore`284285**What it looks like:**286- Committing `.terraform/` directory (plugins).287- Committing `terraform.tfvars` (secrets).288289**Why it fails:**290- Repo bloat.291- Security leak.292293**Correct approach:**294- Standard `.gitignore` for Terraform:295 ```296 .terraform/297 *.tfstate298 *.tfstate.backup299 *.tfvars300 .terraform.lock.hcl (Commit this one!)301 ```302303---304---305306## 7. Quality Checklist307308**Code Quality:**309- [ ] **Formatting:** Run `terraform fmt -recursive`.310- [ ] **Validation:** Run `terraform validate`.311- [ ] **Linting:** Run `tflint` for provider-specific issues.312- [ ] **Docs:** Generate README using `terraform-docs`.313314**Security:**315- [ ] **Secrets:** No plain text secrets (Use KMS/Vault/Secrets Manager).316- [ ] **Encryption:** `encrypted = true` on all storage (EBS, S3, RDS).317- [ ] **Public Access:** Locked down (S3 Block Public Access).318319**Reliability:**320- [ ] **State:** Remote backend configured with locking.321- [ ] **Versions:** Provider and Terraform versions pinned (e.g., `~> 5.0`).322- [ ] **Cleanup:** `destroy` provisioners tested (or protection enabled for DBs).323324## Anti-Patterns325326### State Management Anti-Patterns327328- **Local State**: Using local state files - always use remote backends329- **State Drift**: Manual changes outside Terraform - use only Terraform for changes330- **State Lock Contention**: No state locking - implement proper locking331- **State Corruption**: Editing state files manually - never manually edit state332333### Module Anti-Patterns334335- **Monolithic Modules**: Large, unwieldy modules - split into focused modules336- **Hardcoded Values**: Using values instead of variables - parameterize everything337- **Module Version Chaos**: No version pinning - pin module versions338- **Deep Module Nesting**: Over-nested module structures - keep module hierarchy flat339340### Resource Anti-Patterns341342- **Resource Spam**: Many small resources instead of patterns - use resource grouping343- **Lifecycle Lock**: Resources that can't update - avoid create_before_destroy conflicts344- **Ignored Changes**: Overusing ignore_changes - understand and manage changes345- **Sensitive Data Exposure**: Plain text secrets in state - use sensitive flag346347### Code Organization Anti-Patterns348349- **Flat Structure**: No directory organization - use modular structure350- **Duplication**: Repeated code blocks - use modules and for_each351- **No Formatting**: Unformatted HCL code - use terraform fmt352- **Missing Documentation**: undocumented modules - document all inputs/outputs