CtrlK
BlogDocsLog inGet started
Tessl Logo

terraform-starter

Scaffold Terraform 1.9+ infrastructure with provider configuration (AWS/GCP/Azure), modular structure, remote state, variables, outputs, workspaces, and common resource patterns.

79

Quality

73%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Optimize this skill with Tessl

npx tessl skill review --optimize ./infrastructure/terraform-starter/SKILL.md
SKILL.md
Quality
Evals
Security

Terraform Starter

Scaffold Terraform 1.9+ infrastructure with provider configuration (AWS/GCP/Azure), modular structure, remote state, variables, outputs, workspaces, and common resource patterns.

Prerequisites

  • Terraform >= 1.9
  • Cloud provider CLI authenticated (aws, gcloud, az)
  • S3/GCS bucket for remote state (or Terraform Cloud)
  • Git

Scaffold Command

mkdir -p infra/{modules,environments/dev,environments/staging,environments/production}

# Root module
touch infra/main.tf infra/variables.tf infra/outputs.tf infra/providers.tf infra/backend.tf infra/versions.tf

# Module scaffolds
mkdir -p infra/modules/{networking,compute,database,storage}
touch infra/modules/networking/{main.tf,variables.tf,outputs.tf}
touch infra/modules/compute/{main.tf,variables.tf,outputs.tf}
touch infra/modules/database/{main.tf,variables.tf,outputs.tf}

# Environment tfvars
touch infra/environments/dev/terraform.tfvars
touch infra/environments/staging/terraform.tfvars
touch infra/environments/production/terraform.tfvars

# Gitignore for Terraform
cat <<'EOF' > infra/.gitignore
.terraform/
*.tfstate
*.tfstate.backup
*.tfplan
.terraform.lock.hcl
*.auto.tfvars
EOF

Project Structure

infra/
  main.tf                          # Root module — composes child modules
  variables.tf                     # Input variables
  outputs.tf                       # Outputs from root module
  providers.tf                     # Provider configuration
  backend.tf                       # Remote state backend
  versions.tf                      # Required providers and Terraform version
  modules/
    networking/
      main.tf                      # VPC, subnets, NAT gateway, security groups
      variables.tf
      outputs.tf
    compute/
      main.tf                      # ECS/EC2/GKE/AKS clusters
      variables.tf
      outputs.tf
    database/
      main.tf                      # RDS/CloudSQL/CosmosDB
      variables.tf
      outputs.tf
    storage/
      main.tf                      # S3/GCS buckets
      variables.tf
      outputs.tf
  environments/
    dev/
      terraform.tfvars             # Dev-specific variable values
    staging/
      terraform.tfvars
    production/
      terraform.tfvars

Key Conventions

  • One root module per project. Child modules for logical groupings (networking, compute, database).
  • Use terraform.tfvars files per environment, selected via -var-file.
  • Remote state is mandatory. Use S3 + DynamoDB (AWS), GCS (GCP), or Terraform Cloud.
  • Lock provider versions in versions.tf.
  • Use locals for computed values. Keep variables.tf for user-supplied inputs.
  • Name resources with a consistent prefix: ${var.project}-${var.environment}-<resource>.
  • Tag everything: at minimum Project, Environment, ManagedBy=terraform.
  • Never store secrets in .tfvars files. Use sensitive = true variables or data sources from secret managers.
  • Run terraform fmt before every commit.

Essential Patterns

Version Constraints (versions.tf)

terraform {
  required_version = ">= 1.9"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

Remote State — AWS S3 (backend.tf)

terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "myapp/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Remote State — GCS (backend.tf)

terraform {
  backend "gcs" {
    bucket = "mycompany-terraform-state"
    prefix = "myapp"
  }
}

Provider Configuration (providers.tf)

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Project     = var.project
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

Root Variables (variables.tf)

variable "project" {
  description = "Project name used as resource prefix"
  type        = string
}

variable "environment" {
  description = "Environment name (dev, staging, production)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment must be one of: dev, staging, production."
  }
}

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

Root Module Composition (main.tf)

locals {
  name_prefix = "${var.project}-${var.environment}"
}

module "networking" {
  source = "./modules/networking"

  name_prefix = local.name_prefix
  vpc_cidr    = var.vpc_cidr
  environment = var.environment
}

module "database" {
  source = "./modules/database"

  name_prefix        = local.name_prefix
  subnet_ids         = module.networking.private_subnet_ids
  security_group_ids = [module.networking.db_security_group_id]
  environment        = var.environment
  db_instance_class  = var.db_instance_class
}

module "compute" {
  source = "./modules/compute"

  name_prefix        = local.name_prefix
  subnet_ids         = module.networking.private_subnet_ids
  security_group_ids = [module.networking.app_security_group_id]
  database_url       = module.database.connection_url
  environment        = var.environment
}

VPC Module (modules/networking/main.tf)

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = { Name = "${var.name_prefix}-vpc" }
}

resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  map_public_ip_on_launch = true
  tags = { Name = "${var.name_prefix}-public-${count.index}" }
}

resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = { Name = "${var.name_prefix}-private-${count.index}" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "${var.name_prefix}-igw" }
}

resource "aws_eip" "nat" {
  domain = "vpc"
  tags   = { Name = "${var.name_prefix}-nat-eip" }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id
  tags          = { Name = "${var.name_prefix}-nat" }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
  tags = { Name = "${var.name_prefix}-public-rt" }
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main.id
  }
  tags = { Name = "${var.name_prefix}-private-rt" }
}

resource "aws_route_table_association" "public" {
  count          = length(aws_subnet.public)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  count          = length(aws_subnet.private)
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}

RDS Module (modules/database/main.tf)

resource "aws_db_subnet_group" "main" {
  name       = "${var.name_prefix}-db-subnet"
  subnet_ids = var.subnet_ids
  tags       = { Name = "${var.name_prefix}-db-subnet" }
}

resource "aws_db_instance" "main" {
  identifier     = "${var.name_prefix}-db"
  engine         = "postgres"
  engine_version = "16.4"
  instance_class = var.db_instance_class

  allocated_storage     = 20
  max_allocated_storage = 100
  storage_encrypted     = true

  db_name  = var.db_name
  username = var.db_username
  password = var.db_password

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = var.security_group_ids

  multi_az            = var.environment == "production"
  skip_final_snapshot = var.environment != "production"

  backup_retention_period = var.environment == "production" ? 7 : 1

  tags = { Name = "${var.name_prefix}-db" }
}

S3 Bucket Module (modules/storage/main.tf)

resource "aws_s3_bucket" "main" {
  bucket = "${var.name_prefix}-${var.bucket_suffix}"
  tags   = { Name = "${var.name_prefix}-${var.bucket_suffix}" }
}

resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
  bucket = aws_s3_bucket.main.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "main" {
  bucket                  = aws_s3_bucket.main.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Environment tfvars (environments/dev/terraform.tfvars)

project           = "myapp"
environment       = "dev"
aws_region        = "us-east-1"
vpc_cidr          = "10.0.0.0/16"
db_instance_class = "db.t4g.micro"

Outputs (outputs.tf)

output "vpc_id" {
  description = "VPC ID"
  value       = module.networking.vpc_id
}

output "database_endpoint" {
  description = "RDS endpoint"
  value       = module.database.endpoint
  sensitive   = true
}

Common Commands

# Initialize (download providers, configure backend)
terraform -chdir=infra init

# Format all files
terraform -chdir=infra fmt -recursive

# Validate configuration
terraform -chdir=infra validate

# Plan with environment-specific vars
terraform -chdir=infra plan -var-file=environments/dev/terraform.tfvars -out=dev.tfplan

# Apply a saved plan
terraform -chdir=infra apply dev.tfplan

# Destroy (dev only)
terraform -chdir=infra destroy -var-file=environments/dev/terraform.tfvars

# Import existing resource
terraform -chdir=infra import module.networking.aws_vpc.main vpc-abc123

# Show current state
terraform -chdir=infra show

# List resources in state
terraform -chdir=infra state list

# Upgrade providers
terraform -chdir=infra init -upgrade

Integration Notes

  • Kubernetes: Pair with kubernetes-manifests skill. Terraform provisions the cluster (EKS/GKE/AKS); kubectl/kustomize manages workloads.
  • CI/CD: Pair with github-actions-ci skill. Run terraform plan on PR, terraform apply on merge. Use OIDC for cloud auth.
  • Docker: Terraform can provision ECR/GCR/ACR registries for Docker images built by CI.
  • Secrets: Use aws_secretsmanager_secret / google_secret_manager_secret for runtime secrets. Reference them in compute modules via IAM roles, not hardcoded values.
Repository
achreftlili/deep-dev-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.