name: terraform description: Creates and manages Infrastructure as Code using Terraform/OpenTofu for cloud resource provisioning. Trigger keywords: terraform, opentofu, tofu, infrastructure, IaC, provision, cloud, aws, azure, gcp, module. allowed-tools: Read, Grep, Glob, Edit, Write, Bash
Terraform / OpenTofu
Overview
This skill covers Terraform and OpenTofu configuration for infrastructure provisioning across cloud providers. It includes module design, state management, and infrastructure best practices.
Preferred Tool: OpenTofu - OpenTofu is the open-source fork of Terraform maintained by the Linux Foundation. Prefer tofu commands over terraform when available. The syntax and configuration are fully compatible.
Instructions
1. Plan Infrastructure
- Define resource requirements
- Plan network topology
- Identify dependencies
- Consider multi-region/AZ design
2. Write Terraform Configuration
- Structure code in modules
- Define variables and outputs
- Configure providers
- Set up state backend
3. Implement Best Practices
- Use consistent naming
- Tag all resources
- Implement least privilege
- Enable encryption
4. Manage State
- Configure remote state
- Use state locking
- Plan state migrations
- Handle secrets properly
Best Practices
- Use Modules: Reusable, versioned components
- Remote State: Store state in S3/GCS with locking
- Variables: Parameterize everything
- Workspaces: Separate environments
- Formatting: Use
tofu fmt(orterraform fmt) - Validation: Use
tofu validate(orterraform validate) - Plan Before Apply: Always review changes with
tofu plan
OpenTofu vs Terraform Commands
| Terraform | OpenTofu (Preferred) |
|---|---|
terraform init |
tofu init |
terraform plan |
tofu plan |
terraform apply |
tofu apply |
terraform destroy |
tofu destroy |
terraform fmt |
tofu fmt |
terraform validate |
tofu validate |
Examples
Example 1: AWS VPC Module
# modules/vpc/main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "name" {
description = "Name prefix for resources"
type = string
}
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
locals {
common_tags = merge(var.tags, {
Terraform = "true"
Module = "vpc"
})
}
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.common_tags, {
Name = "${var.name}-vpc"
})
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(local.common_tags, {
Name = "${var.name}-igw"
})
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(local.common_tags, {
Name = "${var.name}-public-${var.availability_zones[count.index]}"
Tier = "public"
})
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
tags = merge(local.common_tags, {
Name = "${var.name}-private-${var.availability_zones[count.index]}"
Tier = "private"
})
}
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = merge(local.common_tags, {
Name = "${var.name}-nat-eip-${count.index}"
})
}
resource "aws_nat_gateway" "main" {
count = length(var.availability_zones)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(local.common_tags, {
Name = "${var.name}-nat-${count.index}"
})
depends_on = [aws_internet_gateway.main]
}
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 "private_subnet_ids" {
description = "Private subnet IDs"
value = aws_subnet.private[*].id
}
Example 2: EKS Cluster Configuration
# main.tf
terraform {
required_version = ">= 1.0"
backend "s3" {
bucket = "my-terraform-state"
key = "eks/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Environment = var.environment
Project = var.project
ManagedBy = "terraform"
}
}
}
module "vpc" {
source = "./modules/vpc"
name = "${var.project}-${var.environment}"
cidr_block = var.vpc_cidr
availability_zones = var.availability_zones
tags = var.tags
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"
cluster_name = "${var.project}-${var.environment}"
cluster_version = "1.28"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
cluster_endpoint_public_access = true
eks_managed_node_groups = {
general = {
desired_size = 2
min_size = 1
max_size = 5
instance_types = ["t3.medium"]
capacity_type = "ON_DEMAND"
labels = {
role = "general"
}
}
}
tags = var.tags
}
Example 3: Variables and Outputs
# variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "project" {
description = "Project name"
type = string
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
# outputs.tf
output "vpc_id" {
description = "VPC ID"
value = module.vpc.vpc_id
}
output "eks_cluster_endpoint" {
description = "EKS cluster endpoint"
value = module.eks.cluster_endpoint
sensitive = true
}
output "eks_cluster_name" {
description = "EKS cluster name"
value = module.eks.cluster_name
}