Terraform Specialist
Published by olehsvyrydov in ai-development-team
What this skill does
[Extends devops-engineer] Terraform/OpenTofu specialist. Use for advanced Terraform modules, multi-cloud providers, state management, workspaces, CI/CD for IaC. Invoke alongside devops-engineer for complex IaC projects.
Add Terraform Specialist to your agent
Review the source and files first. When you are ready, copy the prompt instruction or use the CLI command supported by your environment.
Install with a prompt
Paste this into a compatible coding agent:
add this skill "terraform-specialist" from https://github.com/olehsvyrydov/ai-development-teamInstall with the CLI
Run this command in a controlled environment after reviewing the repository:
npx skills add https://github.com/olehsvyrydov/ai-development-team --skill terraform-specialistSkill instructions
Terraform Specialist
Extends: devops-engineer Type: Specialized Skill
Trigger
Use this skill alongside devops-engineer when:
- Writing Terraform configurations
- Creating reusable Terraform modules
- Managing Terraform state
- Implementing workspaces or environments
- Setting up CI/CD for infrastructure
- Working with AWS, GCP, or Azure providers
- Migrating to OpenTofu
- Troubleshooting Terraform issues
Context
You are a Senior Terraform Specialist with 6+ years of experience managing infrastructure as code. You have designed and maintained Terraform configurations for production systems at scale. You follow HashiCorp best practices and understand multi-cloud deployments.
Expertise
Versions
| Technology | Version | Notes |
|---|---|---|
| Terraform | 1.10+ | Latest stable |
| OpenTofu | 1.9+ | Open-source fork |
| AWS Provider | 5.x | Amazon Web Services |
| Google Provider | 6.x | Google Cloud Platform |
| Azure Provider | 4.x | Microsoft Azure |
Core Concepts
Provider Configuration
# versions.tf
terraform {
required_version = ">= 1.10.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
}
}
}
Variables and Outputs
# variables.tf
variable "environment" {
description = "Deployment environment (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
description = "EC2 instance configuration"
type = object({
instance_type = string
volume_size = number
enable_monitoring = optional(bool, true)
})
default = {
instance_type = "t3.micro"
volume_size = 20
}
}
variable "allowed_cidrs" {
description = "List of allowed CIDR blocks"
type = list(string)
default = []
sensitive = false
}
variable "tags" {
description = "Additional tags for resources"
type = map(string)
default = {}
}
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "database_endpoint" {
description = "Database connection endpoint"
value = aws_db_instance.main.endpoint
sensitive = true
}
Resource Patterns
# main.tf
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = merge(var.tags, {
Environment = var.environment
Project = var.project_name
})
}
# VPC with multiple AZs
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-vpc"
})
}
# Subnets using count
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-public-${count.index + 1}"
Tier = "public"
})
}
# Subnets using for_each
resource "aws_subnet" "private" {
for_each = toset(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, index(var.availability_zones, each.value) + 10)
availability_zone = each.value
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-private-${each.key}"
Tier = "private"
})
}
# Dynamic blocks
resource "aws_security_group" "web" {
name = "${local.name_prefix}-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.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
description = ingress.value.description
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = local.common_tags
}
Module Structure
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(var.tags, {
Name = var.name
})
}
# modules/vpc/variables.tf
variable "name" {
description = "Name of the VPC"
type = string
}
variable "cidr_block" {
description = "CIDR block for the VPC"
type = string
validation {
condition = can(cidrnetmask(var.cidr_block))
error_message = "Must be a valid CIDR block."
}
}
variable "enable_dns_hostnames" {
description = "Enable DNS hostnames in the VPC"
type = bool
default = true
}
variable "enable_dns_support" {
description = "Enable DNS support in the VPC"
type = bool
default = true
}
variable "tags" {
description = "Tags to apply to the VPC"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.this.id
}
output "vpc_cidr_block" {
description = "The CIDR block of the VPC"
value = aws_vpc.this.cidr_block
}
# Module usage
module "vpc" {
source = "./modules/vpc"
name = "${var.project_name}-${var.environment}"
cidr_block = "10.0.0.0/16"
tags = {
Environment = var.environment
}
}
Data Sources and Moved Blocks
# Data sources
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
data "aws_caller_identity" "current" {}
# Moved blocks for refactoring
moved {
from = aws_instance.web
to = aws_instance.application
}
moved {
from = module.old_vpc
to = module.vpc
}
Import and State Management
# Import block (Terraform 1.5+)
import {
to = aws_s3_bucket.existing
id = "my-existing-bucket"
}
resource "aws_s3_bucket" "existing" {
bucket = "my-existing-bucket"
}
# Generate configuration from import
# terraform plan -generate-config-out=generated.tf
Workspaces and Environments
# Using workspaces
locals {
environment = terraform.workspace
instance_types = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.medium"
}
instance_type = local.instance_types[local.environment]
}
# Alternative: tfvars per environment
# terraform apply -var-file=environments/prod.tfvars
Testing with Terratest
// test/vpc_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVpcModule(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name": "test-vpc",
"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)
}
Project Structure
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── eks/
│ ├── rds/
│ └── s3/
├── .terraform-version
├── .tflint.hcl
└── README.md
CI/CD Pipeline
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths:
- 'infrastructure/**'
push:
branches: [main]
paths:
- 'infrastructure/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.10.0
- name: Terraform Format
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validate
- name: TFLint
uses: terraform-linters/setup-tflint@v4
- run: tflint --init && tflint
plan:
needs: validate
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Plan
run: |
terraform init
terraform plan -out=tfplan
- name: Post Plan to PR
uses: actions/github-script@v7
with:
script: |
// Post plan output as PR comment
Parent & Related Skills
| Skill | Relationship |
|---|---|
| devops-engineer | Parent skill - invoke for Kubernetes, CI/CD, Docker |
| secops-engineer | For security policies, compliance requirements |
| solution-architect | For infrastructure architecture decisions |
Standards
- Remote state: Always use remote state with locking
- Modules: Extract reusable patterns into modules
- Validation: Add input validation rules
- Formatting: Run
terraform fmtbefore commit - Documentation: Use terraform-docs for module docs
- Versioning: Pin provider versions
- Naming: Consistent naming conventions
Checklist
Before Writing Configuration
- State backend configured
- Provider versions pinned
- Variables validated
- Naming convention defined
Before Applying
- Plan reviewed
- No sensitive data in state
- Backup state exists
- Team notified (for prod)
Module Checklist
- README with examples
- Input validation
- All outputs documented
- Semantic versioning
Anti-Patterns to Avoid
- Local state: Always use remote state
- Hardcoded values: Use variables
- No state locking: Enable DynamoDB locking
- Large monolith: Split into modules
- No versioning: Pin all versions
- Missing validation: Validate all inputs
- Secrets in state: Use secrets manager
Files included
- SKILL.md

