Overview
Expert in Infrastructure as Code using Terraform and OpenTofu. Specializes in module design, state management, multi-cloud deployments, and CI/CD integration. Handles complex infrastructure patterns including multi-environment setups, remote state backends, and secure secrets management.
When to Use
- Setting up new Terraform projects and workspaces
- Designing reusable Terraform modules
- Managing state files and remote backends
- Implementing multi-environment (dev/staging/prod) infrastructure
- Migrating existing infrastructure to Terraform
- Troubleshooting state drift and plan failures
- Integrating Terraform with CI/CD pipelines
- Implementing security best practices (secrets, IAM, policies)
Capabilities
Project Structure
- Module-based architecture design
- Workspace vs directory structure strategies
- Variable and output organization
- Provider configuration and version constraints
- Backend configuration for remote state
Module Development
- Reusable module patterns
- Input validation and type constraints
- Output design for module composition
- Local modules vs registry modules
- Module versioning and publishing
State Management
- Remote state backends (S3, GCS, Azure Blob, Terraform Cloud)
- State locking mechanisms
- State migration and manipulation
- Import existing resources
- Handling state drift
Multi-Environment Patterns
- Workspace-based environments
- Directory-based environments
- Terragrunt for DRY infrastructure
- Environment-specific variables
- Promotion workflows
Security
- Sensitive variable handling
- IAM role design for Terraform
- Policy as Code (Sentinel, OPA)
- Secrets management integration (Vault, AWS Secrets Manager)
- Least privilege principles
CI/CD Integration
- GitHub Actions for Terraform
- Atlantis for PR-based workflows
- Terraform Cloud/Enterprise
- Plan/Apply automation
- Cost estimation integration
Dependencies
Works well with:
aws-solutions-architect - AWS resource patterns
kubernetes-orchestrator - K8s infrastructure
github-actions-pipeline-builder - CI/CD automation
site-reliability-engineer - Production infrastructure
Examples
Project Structure
terraform/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── eks/
│ └── rds/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
└── shared/
└── provider.tf
Root Module with Locals
hcl
1# environments/prod/main.tf
2terraform {
3 required_version = ">= 1.5.0"
4
5 required_providers {
6 aws = {
7 source = "hashicorp/aws"
8 version = "~> 5.0"
9 }
10 }
11
12 backend "s3" {
13 bucket = "mycompany-terraform-state"
14 key = "prod/terraform.tfstate"
15 region = "us-west-2"
16 encrypt = true
17 dynamodb_table = "terraform-locks"
18 }
19}
20
21locals {
22 environment = "prod"
23 project = "myapp"
24
25 common_tags = {
26 Environment = local.environment
27 Project = local.project
28 ManagedBy = "terraform"
29 }
30}
31
32module "vpc" {
33 source = "../../modules/vpc"
34
35 environment = local.environment
36 cidr_block = "10.0.0.0/16"
37 tags = local.common_tags
38}
39
40module "eks" {
41 source = "../../modules/eks"
42
43 environment = local.environment
44 vpc_id = module.vpc.vpc_id
45 private_subnet_ids = module.vpc.private_subnet_ids
46 cluster_version = "1.29"
47 tags = local.common_tags
48}
Reusable Module with Validation
hcl
1# modules/vpc/variables.tf
2variable "environment" {
3 type = string
4 description = "Environment name (dev, staging, prod)"
5
6 validation {
7 condition = contains(["dev", "staging", "prod"], var.environment)
8 error_message = "Environment must be dev, staging, or prod."
9 }
10}
11
12variable "cidr_block" {
13 type = string
14 description = "VPC CIDR block"
15
16 validation {
17 condition = can(cidrhost(var.cidr_block, 0))
18 error_message = "Must be a valid CIDR block."
19 }
20}
21
22variable "availability_zones" {
23 type = list(string)
24 description = "List of AZs to use"
25 default = ["us-west-2a", "us-west-2b", "us-west-2c"]
26}
27
28variable "enable_nat_gateway" {
29 type = bool
30 description = "Enable NAT Gateway for private subnets"
31 default = true
32}
33
34variable "tags" {
35 type = map(string)
36 description = "Tags to apply to all resources"
37 default = {}
38}
Module with Dynamic Blocks
hcl
1# modules/security-group/main.tf
2resource "aws_security_group" "this" {
3 name = var.name
4 description = var.description
5 vpc_id = var.vpc_id
6
7 dynamic "ingress" {
8 for_each = var.ingress_rules
9 content {
10 from_port = ingress.value.from_port
11 to_port = ingress.value.to_port
12 protocol = ingress.value.protocol
13 cidr_blocks = ingress.value.cidr_blocks
14 description = ingress.value.description
15 }
16 }
17
18 egress {
19 from_port = 0
20 to_port = 0
21 protocol = "-1"
22 cidr_blocks = ["0.0.0.0/0"]
23 }
24
25 tags = merge(var.tags, {
26 Name = var.name
27 })
28}
Remote State Data Source
hcl
1# Reference another environment's state
2data "terraform_remote_state" "shared" {
3 backend = "s3"
4
5 config = {
6 bucket = "mycompany-terraform-state"
7 key = "shared/terraform.tfstate"
8 region = "us-west-2"
9 }
10}
11
12# Use outputs from shared state
13resource "aws_instance" "app" {
14 ami = data.terraform_remote_state.shared.outputs.base_ami_id
15 instance_type = "t3.medium"
16 subnet_id = data.terraform_remote_state.shared.outputs.private_subnet_id
17}
GitHub Actions CI/CD
yaml
1# .github/workflows/terraform.yml
2name: Terraform
3
4on:
5 pull_request:
6 paths:
7 - 'terraform/**'
8 push:
9 branches: [main]
10 paths:
11 - 'terraform/**'
12
13env:
14 TF_VERSION: 1.6.0
15 AWS_REGION: us-west-2
16
17jobs:
18 plan:
19 runs-on: ubuntu-latest
20 permissions:
21 contents: read
22 pull-requests: write
23 id-token: write # For OIDC
24
25 steps:
26 - uses: actions/checkout@v4
27
28 - name: Configure AWS credentials
29 uses: aws-actions/configure-aws-credentials@v4
30 with:
31 role-to-assume: arn:aws:iam::123456789:role/terraform-github-actions
32 aws-region: ${{ env.AWS_REGION }}
33
34 - uses: hashicorp/setup-terraform@v3
35 with:
36 terraform_version: ${{ env.TF_VERSION }}
37
38 - name: Terraform Init
39 working-directory: terraform/environments/prod
40 run: terraform init
41
42 - name: Terraform Plan
43 working-directory: terraform/environments/prod
44 run: terraform plan -out=tfplan
45
46 - name: Upload Plan
47 uses: actions/upload-artifact@v4
48 with:
49 name: tfplan
50 path: terraform/environments/prod/tfplan
51
52 apply:
53 needs: plan
54 runs-on: ubuntu-latest
55 if: github.ref == 'refs/heads/main' && github.event_name == 'push'
56 environment: production
57
58 steps:
59 - uses: actions/checkout@v4
60
61 - name: Configure AWS credentials
62 uses: aws-actions/configure-aws-credentials@v4
63 with:
64 role-to-assume: arn:aws:iam::123456789:role/terraform-github-actions
65 aws-region: ${{ env.AWS_REGION }}
66
67 - uses: hashicorp/setup-terraform@v3
68 with:
69 terraform_version: ${{ env.TF_VERSION }}
70
71 - name: Download Plan
72 uses: actions/download-artifact@v4
73 with:
74 name: tfplan
75 path: terraform/environments/prod
76
77 - name: Terraform Apply
78 working-directory: terraform/environments/prod
79 run: terraform apply -auto-approve tfplan
Import Existing Resources
bash
1# Import existing AWS resource into state
2terraform import aws_s3_bucket.existing my-existing-bucket
3
4# Import using for_each key
5terraform import 'aws_iam_user.users["alice"]' alice
6
7# Generate configuration from import (Terraform 1.5+)
8terraform plan -generate-config-out=generated.tf
Handling Sensitive Values
hcl
1# Reference secrets from AWS Secrets Manager
2data "aws_secretsmanager_secret_version" "db_password" {
3 secret_id = "prod/db/password"
4}
5
6resource "aws_db_instance" "main" {
7 # ... other config ...
8 password = data.aws_secretsmanager_secret_version.db_password.secret_string
9}
10
11# Mark outputs as sensitive
12output "db_connection_string" {
13 value = "postgres://admin:${aws_db_instance.main.password}@${aws_db_instance.main.endpoint}"
14 sensitive = true
15}
Best Practices
- Use remote state - Never store state locally for team projects
- Enable state locking - Prevent concurrent modifications
- Version pin providers - Use
~> constraints, not >=
- Separate environments - Use directories or workspaces, not branches
- Module everything reusable - But don't over-abstract
- Validate inputs - Use variable validation blocks
- Use data sources - Reference existing resources instead of hardcoding
- Tag all resources - Apply consistent tags for cost tracking
- Review plans carefully - Especially for destroy operations
Common Pitfalls
- State file conflicts - Multiple people running terraform simultaneously
- Hardcoded values - Not using variables for environment differences
- Circular dependencies - Resources depending on each other
- Missing dependencies - Not using
depends_on when implicit deps aren't enough
- Large state files - Not breaking up large infrastructure
- Secrets in state - State contains sensitive values, encrypt at rest
- Provider version drift - Different team members using different versions
- Not using -target carefully - Can cause drift, use sparingly