Marketplace

opentofu-coder

This skill guides writing Infrastructure as Code using OpenTofu (open-source Terraform fork). Use when creating .tf files, managing cloud infrastructure, configuring providers, or designing reusable modules.

allowed_tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash, WebSearch

$ Installer

git clone https://github.com/majesticlabs-dev/majestic-marketplace /tmp/majestic-marketplace && cp -r /tmp/majestic-marketplace/plugins/majestic-devops/skills/opentofu-coder ~/.claude/skills/majestic-marketplace

// tip: Run this command in your terminal to install the skill


name: opentofu-coder description: This skill guides writing Infrastructure as Code using OpenTofu (open-source Terraform fork). Use when creating .tf files, managing cloud infrastructure, configuring providers, or designing reusable modules. allowed-tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash, WebSearch

OpenTofu Coder

⚠️ SIMPLICITY FIRST - Default to Flat Files

ALWAYS start with the simplest approach. Only add complexity when explicitly requested.

Simple (DEFAULT) vs Overengineered

Aspect✅ Simple (Default)❌ Overengineered
StructureFlat .tf files in one directoryNested modules/ + environments/ directories
ModulesNone or only remote registry modulesCustom local modules for simple resources
EnvironmentsWorkspaces OR single tfvarsDuplicate directory per environment
VariablesInline defaults, minimal tfvarsComplex variable hierarchies
File count3-5 .tf files total15+ files across nested directories

When to Use Simple Approach (90% of cases)

  • Managing 1-5 resources of each type
  • Single provider, single region
  • Small team or solo developer
  • Standard infrastructure patterns

When Complexity is Justified (10% of cases)

  • Enterprise multi-region, multi-account
  • Reusable modules shared across teams
  • Complex dependency chains
  • User explicitly requests modular structure

Rule: If you can define everything in 5 flat .tf files, DO IT.

Simple Project Structure (DEFAULT)

infra/
├── main.tf           # All resources
├── variables.tf      # Input variables
├── outputs.tf        # Outputs
├── versions.tf       # Provider versions
└── terraform.tfvars  # Variable values (gitignored)

Overview

OpenTofu is a community-driven, open-source fork of Terraform under MPL-2.0 license, maintained by the Linux Foundation. It uses HashiCorp Configuration Language (HCL) for declarative infrastructure management across cloud providers.

Core Philosophy

Prioritize:

  • Declarative over imperative: Describe desired state, not steps
  • Idempotency: Apply safely multiple times with same result
  • Modularity: Compose infrastructure from reusable modules
  • State as truth: State file is the source of truth for managed resources
  • Immutable infrastructure: Replace resources rather than mutate in place

HCL Syntax Essentials

Resource Blocks

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name        = "${var.project}-web"
    Environment = var.environment
  }
}

Data Sources

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  owners = ["099720109477"]  # Canonical
}

Variables

variable "environment" {
  description = "Deployment environment (dev, staging, prod)"
  type        = string
  default     = "dev"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "instance_types" {
  description = "Map of environment to instance type"
  type        = map(string)
  default = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.medium"
  }
}

Outputs

output "instance_ip" {
  description = "Public IP of the web instance"
  value       = aws_instance.web.public_ip
  sensitive   = false
}

output "database_password" {
  description = "Generated database password"
  value       = random_password.db.result
  sensitive   = true
}

Locals

locals {
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "OpenTofu"
  }

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

Meta-Arguments

count - Create Multiple Instances

resource "aws_instance" "server" {
  count = var.server_count

  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name = "${local.name_prefix}-server-${count.index}"
  }
}

for_each - Create from Map/Set

resource "aws_iam_user" "users" {
  for_each = toset(var.user_names)

  name = each.value
  path = "/users/"
}

resource "aws_security_group_rule" "ingress" {
  for_each = var.ingress_rules

  type              = "ingress"
  from_port         = each.value.port
  to_port           = each.value.port
  protocol          = each.value.protocol
  cidr_blocks       = each.value.cidr_blocks
  security_group_id = aws_security_group.main.id
}

depends_on - Explicit Dependencies

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = var.instance_type

  depends_on = [
    aws_db_instance.database,
    aws_elasticache_cluster.cache
  ]
}

lifecycle - Control Resource Behavior

resource "aws_instance" "critical" {
  ami           = var.ami_id
  instance_type = var.instance_type

  lifecycle {
    prevent_destroy = true
    create_before_destroy = true
    ignore_changes = [
      tags["LastUpdated"],
      user_data
    ]
  }
}

# Replace when AMI changes
resource "aws_instance" "immutable" {
  ami           = var.ami_id
  instance_type = var.instance_type

  lifecycle {
    replace_triggered_by = [
      null_resource.ami_trigger
    ]
  }
}

Module Design

Module Structure

modules/
└── vpc/
    ├── main.tf          # Primary resources
    ├── variables.tf     # Input variables
    ├── outputs.tf       # Output values
    ├── versions.tf      # Required providers
    └── README.md        # Documentation

Calling Modules

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

  cidr_block  = "10.0.0.0/16"
  environment = var.environment

  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

# Remote module with version
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = local.cluster_name
  cluster_version = "1.29"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
}

Module Best Practices

  • Expose minimal, clear interface of variables
  • Use sensible defaults where possible
  • Document all variables and outputs
  • Avoid over-generic "god" modules
  • Prefer composition over configuration flags
  • Version pin remote modules

State Management

Remote Backend (S3)

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

OpenTofu State Encryption (Unique Feature)

terraform {
  encryption {
    key_provider "pbkdf2" "main" {
      passphrase = var.state_encryption_passphrase
    }

    method "aes_gcm" "encrypt" {
      keys = key_provider.pbkdf2.main
    }

    state {
      method   = method.aes_gcm.encrypt
      enforced = true
    }

    plan {
      method   = method.aes_gcm.encrypt
      enforced = true
    }
  }
}

State Commands

# List resources in state
tofu state list

# Show specific resource
tofu state show aws_instance.web

# Move resource (refactoring)
tofu state mv aws_instance.old aws_instance.new

# Remove from state (without destroying)
tofu state rm aws_instance.imported

# Import existing resource
tofu import aws_instance.web i-1234567890abcdef0

Provider Configuration

AWS Provider

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

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = local.common_tags
  }
}

# Multiple provider configurations
provider "aws" {
  alias  = "us_west"
  region = "us-west-2"
}

resource "aws_instance" "west" {
  provider = aws.us_west
  # ...
}

Provider Authentication

# Environment variables (preferred)
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
# AWS_PROFILE for named profiles

# Named profile from ~/.aws/credentials (recommended)
provider "aws" {
  region  = var.aws_region
  profile = "suppli"  # Uses [suppli] section from ~/.aws/credentials
}

# Or explicit (NOT recommended for secrets)
provider "aws" {
  region     = var.aws_region
  access_key = var.aws_access_key  # Use env vars instead
  secret_key = var.aws_secret_key
}

# Assume role
provider "aws" {
  region = var.aws_region

  assume_role {
    role_arn     = "arn:aws:iam::123456789012:role/DeployRole"
    session_name = "TofuDeployment"
  }
}

Environment Strategies

Workspaces

# Create and switch workspaces
tofu workspace new dev
tofu workspace new staging
tofu workspace new prod

# Switch workspace
tofu workspace select prod

# List workspaces
tofu workspace list
# Use workspace in configuration
locals {
  environment = terraform.workspace

  instance_type = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.medium"
  }[terraform.workspace]
}

Directory-Based Environments (Alternative)

infrastructure/
├── modules/           # Shared modules
│   ├── vpc/
│   └── eks/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       └── terraform.tfvars

CLI Workflow

# Initialize working directory
tofu init

# Validate configuration
tofu validate

# Format code
tofu fmt -recursive

# Preview changes
tofu plan -out=plan.tfplan

# Apply changes
tofu apply plan.tfplan

# Destroy infrastructure
tofu destroy

# Show current state
tofu show

# Refresh state from actual infrastructure
tofu refresh

Best Practices Checklist

When writing OpenTofu/Terraform code:

  • Use remote backend with locking for team use
  • Enable state encryption (OpenTofu feature)
  • Never commit .tfstate or .tfvars with secrets to VCS
  • Pin provider and module versions
  • Use tofu plan before every apply
  • Use lifecycle.prevent_destroy for critical resources
  • Document all variables and outputs
  • Use locals for computed values and tags
  • Prefer for_each over count for named resources
  • Use validation blocks for variable constraints
  • Store secrets in secret managers, not in code

Common Patterns

Conditional Resources

resource "aws_eip" "static" {
  count = var.create_elastic_ip ? 1 : 0

  instance = aws_instance.web.id
}

Dynamic Blocks

resource "aws_security_group" "main" {
  name = "${local.name_prefix}-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

References

For detailed patterns and examples:

Repository

majesticlabs-dev
majesticlabs-dev
Author
majesticlabs-dev/majestic-marketplace/plugins/majestic-devops/skills/opentofu-coder
13
Stars
0
Forks
Updated2d ago
Added5d ago