terraform-specialist

Extends: devops-engineer Type: Specialized Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "terraform-specialist" with this command: npx skills add olehsvyrydov/ai-development-team/olehsvyrydov-ai-development-team-terraform-specialist

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 fmt before 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

legal-counsel

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

accountant

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

backend-reviewer

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

frontend-reviewer

No summary provided by upstream source.

Repository SourceNeeds Review