Terraform Infrastructure as Code Expert
Purpose
Provide expert guidance on Terraform for infrastructure provisioning, state management, modules, and multi-cloud deployments.
When to Use This Skill
- Writing Terraform configurations
- Creating reusable modules
- Managing infrastructure state
- Multi-cloud deployments
- Infrastructure refactoring
- CI/CD integration
- Best practices and patterns
Project Structure
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── terraform.tfvars
├── modules/
│ ├── vpc/
│ ├── compute/
│ └── database/
└── environments/
├── dev/
├── staging/
└── production/
Best Practices
1. Provider Configuration
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
2. Variables
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {}
}
3. Outputs
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "database_endpoint" {
description = "Database endpoint"
value = aws_db_instance.main.endpoint
sensitive = true
}
4. Reusable Module
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
var.tags,
{
Name = "${var.name}-vpc"
}
)
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.name}-public-${count.index + 1}"
Type = "public"
}
)
}
# modules/vpc/variables.tf
variable "name" {
description = "VPC name"
type = string
}
variable "cidr_block" {
description = "VPC CIDR block"
type = string
}
variable "public_subnet_cidrs" {
description = "Public subnet CIDR blocks"
type = list(string)
}
variable "availability_zones" {
description = "Availability zones"
type = list(string)
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
5. Using Modules
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
availability_zones = ["us-east-1a", "us-east-1b"]
tags = {
Environment = "production"
}
}
module "compute" {
source = "./modules/compute"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnet_ids
instance_type = var.instance_type
key_name = var.key_name
}
Advanced Patterns
1. Dynamic Blocks
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = var.vpc_id
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
}
}
}
2. For Expressions
locals {
subnet_ids = [for subnet in aws_subnet.private : subnet.id]
instance_ips = {
for instance in aws_instance.app :
instance.tags["Name"] => instance.private_ip
}
}
3. Conditional Resources
resource "aws_db_instance" "replica" {
count = var.create_replica ? 1 : 0
replicate_source_db = aws_db_instance.main.identifier
instance_class = var.replica_instance_class
}
4. Data Sources
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
}
5. Remote State Data
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "vpc/terraform.tfstate"
region = "us-east-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id
}
State Management
# Initialize
terraform init
# Validate
terraform validate
# Plan
terraform plan -out=tfplan
# Apply
terraform apply tfplan
# Import existing resource
terraform import aws_instance.example i-1234567890
# Show state
terraform show
# List resources
terraform state list
# Move resource
terraform state mv aws_instance.old aws_instance.new
# Remove from state
terraform state rm aws_instance.example
# Refresh state
terraform refresh
Workspaces
# Create workspace
terraform workspace new production
# List workspaces
terraform workspace list
# Switch workspace
terraform workspace select production
# Delete workspace
terraform workspace delete dev
Testing
# Using terratest (Go)
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
)
func TestVPCModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name": "test",
"cidr_block": "10.0.0.0/16",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
}
This skill ensures maintainable, scalable infrastructure as code.