
Opentofu Guide
- 185 installs
- 50 repo stars
- Updated June 18, 2026
- josiahsiegel/claude-plugin-marketplace
Author, review, and migrate OpenTofu modules and state backends when provisioning cloud resources, wiring CI plans/applies, or converting Terraform roots to OpenTofu.
About
OpenTofu guide for declarative infrastructure: module layout, provider versioning, remote state, and CI-driven plan/apply flows. Supports greenfield stacks and Terraform-to-OpenTofu migrations with attention to drift, secrets handling, and environment promotion practices.
- OpenTofu vs Terraform fork differences
- Module structure and provider pinning
- Remote state and locking patterns
- CI plan/apply workflow templates
- Safe import and migration steps
Opentofu Guide by the numbers
- 185 all-time installs (skills.sh)
- +6 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #419 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill opentofu-guideAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 185 |
|---|---|
| repo stars | ★ 50 |
| Last updated | June 18, 2026 |
| Repository | josiahsiegel/claude-plugin-marketplace ↗ |
What it does
Author, review, and migrate OpenTofu modules and state backends when provisioning cloud resources, wiring CI plans/applies, or converting Terraform roots to OpenTofu.
Files
<!-- Progressive Disclosure References:
- references/opentofu-1.10-features.md - OCI registry, native S3 locking, deprecation warnings
- references/opentofu-1.11-features.md - Ephemeral resources, enabled meta-argument
- references/state-encryption.md - Complete state encryption guide with KMS integration
-->
🚨 CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
Examples:
- ❌ WRONG:
D:/repos/project/file.tsx - ✅ CORRECT:
D:\repos\project\file.tsx
This applies to:
- Edit tool file_path parameter
- Write tool file_path parameter
- All file operations on Windows systems
Documentation Guidelines
NEVER create new documentation files unless explicitly requested by the user.
- Priority: Update existing README.md files rather than creating new documentation
- Repository cleanliness: Keep repository root clean - only README.md unless user requests otherwise
- Style: Documentation should be concise, direct, and professional - avoid AI-generated tone
- User preference: Only create additional .md files when user specifically asks for documentation
---
OpenTofu Expertise and Migration Guide
Overview
OpenTofu is the open-source fork of Terraform, created in 2023 after HashiCorp changed Terraform's license from MPL 2.0 to BSL (Business Source License). OpenTofu is stewarded by the Linux Foundation and maintains full compatibility with Terraform 1.5.x while adding community-driven features.
Key Differences (2025)
Licensing
Terraform (HashiCorp):
- BSL (Business Source License) since August 2023
- Restrictions on commercial use for competing products
- IBM acquired HashiCorp in 2024
OpenTofu:
- MPL 2.0 (Mozilla Public License)
- True open-source
- Linux Foundation governance
- Community-driven development
Feature Innovations (2025)
OpenTofu 1.7 Features:
- State Encryption: Client-side encryption (community requested for 5+ years)
- Loop-able Import Blocks: for_each in import blocks
- Dynamic Provider Functions: Provider-defined functions support
- Early Variable Evaluation: Variables in terraform block
OpenTofu 1.8 Features (Latest):
- OpenTofu-Specific Overrides: Balance compatibility with innovation
- Early Variable Evaluation Expanded: Use variables/locals in module sources
- Enhanced Provider Support: Improved provider SDK
Terraform Advantages:
- HCP Terraform: Cloud platform with Stacks, HYOK, Private VCS Access
- Enterprise Support: Direct HashiCorp/IBM support
- Larger Ecosystem: More established marketplace
- Sentinel Policies: Policy-as-code framework (350+ NIST policies)
Compatibility
100% Compatible:
- HCL syntax (same language)
- Provider ecosystem (same registry access)
- State file format (Terraform 1.5.x)
- Module structure
- CLI commands
Migration Path:
- Drop-in replacement for Terraform 1.5.x
- No code changes required
- State files portable (with encryption consideration)
When to Use OpenTofu vs Terraform
Choose OpenTofu When:
1. Open-Source Requirements:
- Organization policy requires open-source tools
- Want vendor neutrality
- Concerned about future license changes
2. State Encryption Needed:
- Compliance requires client-side encryption
- Want encryption without HCP Terraform
- Multi-cloud encryption requirements
3. Cost Optimization:
- Want free state encryption
- No need for HCP Terraform features
- Budget constraints on tooling
4. Community-Driven:
- Want to influence roadmap
- Prefer Linux Foundation governance
- Value community contributions
Choose Terraform When:
1. Enterprise Features Required:
- Need HCP Terraform Stacks
- Require HYOK (Hold Your Own Key)
- Want Private VCS Access
- Need Sentinel policy enforcement
2. Enterprise Support:
- Want direct HashiCorp/IBM support
- Need SLA guarantees
- Require compliance certifications
3. Advanced Features:
- Ephemeral values (1.10+)
- Terraform Query (1.14+)
- Actions blocks (1.14+)
- Latest provider features first
4. Established Ecosystem:
- Existing HCP Terraform investment
- Tight integration needs
- Mature tooling requirements
Migration from Terraform to OpenTofu
Step 1: Assess Compatibility
# Check Terraform version
terraform version
# Must be 1.5.x or compatible
# Check provider versions
terraform providers
# All providers compatible (same registry)Step 2: Install OpenTofu
Windows:
# Chocolatey
choco install opentofu
# Scoop
scoop install opentofu
# Manual
# Download from https://github.com/opentofu/opentofu/releasesmacOS:
# Homebrew
brew install opentofu
# Manual
curl -L https://github.com/opentofu/opentofu/releases/download/v1.8.0/tofu_1.8.0_darwin_amd64.tar.gz | tar xz
sudo mv tofu /usr/local/bin/Linux:
# Snap
snap install opentofu --classic
# Debian/Ubuntu
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh
# Manual
wget https://github.com/opentofu/opentofu/releases/download/v1.8.0/tofu_1.8.0_linux_amd64.tar.gz
tar -xzf tofu_1.8.0_linux_amd64.tar.gz
sudo mv tofu /usr/local/bin/Step 3: Test Compatibility
# Navigate to Terraform directory
cd /path/to/terraform/project
# Initialize with OpenTofu (non-destructive)
tofu init
# Validate configuration
tofu validate
# Generate plan (compare with Terraform plan)
tofu planStep 4: Migrate State (Optional)
If NOT using state encryption:
# State is compatible - no migration needed
# Just switch from 'terraform' to 'tofu' commands
# Verify state
tofu showIf ENABLING state encryption:
# Configure encryption in .tofu file
cat > .tofu <<EOF
encryption {
state {
method = "aes_gcm"
keys {
name = "my_key"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
plan {
method = "aes_gcm"
keys {
name = "my_key"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
}
EOF
# Set encryption key
export TOFU_ENCRYPTION_KEY="your-secure-passphrase"
# Migrate state (automatically encrypts)
tofu init -migrate-stateStep 5: Update CI/CD
GitHub Actions:
# Before (Terraform)
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0
# After (OpenTofu)
- uses: opentofu/setup-opentofu@v1
with:
tofu_version: 1.8.0
# Or manual install
- name: Install OpenTofu
run: |
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh
tofu versionAzure DevOps:
# Before
- task: TerraformInstaller@0
inputs:
terraformVersion: '1.5.0'
# After
- task: Bash@3
displayName: 'Install OpenTofu'
inputs:
targetType: 'inline'
script: |
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh
tofu versionGitLab CI:
# Before
image: hashicorp/terraform:1.5.0
# After
image: ghcr.io/opentofu/opentofu:1.8.0State Encryption (OpenTofu Exclusive)
Configuration
Basic Encryption:
# .tofu or terraform.tf
encryption {
state {
method = "aes_gcm"
keys {
name = "primary_key"
passphrase = env.TOFU_STATE_ENCRYPTION_KEY
}
}
}Key Rotation:
encryption {
state {
method = "aes_gcm"
keys {
# New key
name = "key_v2"
passphrase = env.TOFU_KEY_V2
# Old key (for decryption)
fallback {
name = "key_v1"
passphrase = env.TOFU_KEY_V1
}
}
}
}Cloud KMS Integration:
# AWS KMS
encryption {
state {
method = "aws_kms"
keys {
name = "aws_key"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
}
# Azure Key Vault
encryption {
state {
method = "azurerm_key_vault"
keys {
name = "azure_key"
key_vault_key_id = "https://myvault.vault.azure.net/keys/mykey/version"
}
}
}
# GCP KMS
encryption {
state {
method = "gcp_kms"
keys {
name = "gcp_key"
kms_crypto_key = "projects/PROJECT_ID/locations/LOCATION/keyRings/RING/cryptoKeys/KEY"
}
}
}Best Practices
1. Store Keys Securely:
# Never commit keys
echo "TOFU_ENCRYPTION_KEY=xxx" >> .env
echo ".env" >> .gitignore
# Use CI/CD secrets
# GitHub: Repository Settings → Secrets
# Azure DevOps: Pipeline → Variables → Secret2. Rotate Keys Regularly:
# Generate new key
NEW_KEY=$(openssl rand -base64 32)
# Add to fallback, update configs
# Migrate state
tofu init -migrate-state3. Backup Unencrypted State:
# Before enabling encryption
terraform state pull > backup-unencrypted.tfstate
# Enable encryption
tofu init -migrate-state
# Verify
tofu state pull # Should be encrypted in backendLoop-able Import Blocks (OpenTofu 1.7+)
Terraform 1.5+ (Single Imports):
import {
to = azurerm_resource_group.example
id = "/subscriptions/.../resourceGroups/my-rg"
}OpenTofu 1.7+ (Loop Imports):
# Import multiple resource groups
locals {
resource_groups = {
"rg1" = "/subscriptions/.../resourceGroups/rg1"
"rg2" = "/subscriptions/.../resourceGroups/rg2"
"rg3" = "/subscriptions/.../resourceGroups/rg3"
}
}
import {
for_each = local.resource_groups
to = azurerm_resource_group.imported[each.key]
id = each.value
}
resource "azurerm_resource_group" "imported" {
for_each = local.resource_groups
name = each.key
location = "eastus"
}Early Variable Evaluation (OpenTofu 1.7+)
Terraform 1.5.x:
# Variables NOT allowed in terraform block
terraform {
required_version = ">= 1.5.0" # Static only
backend "azurerm" {
resource_group_name = "terraform-state" # Static only
storage_account_name = "tfstate"
}
}OpenTofu 1.7+:
# Variables allowed in terraform block
variable "environment" {
type = string
}
terraform {
required_version = ">= 1.7.0"
backend "azurerm" {
resource_group_name = "terraform-state-${var.environment}"
storage_account_name = "tfstate${var.environment}"
key = "${var.environment}.tfstate"
}
}OpenTofu 1.8+ (Module Sources):
variable "module_version" {
type = string
default = "v1.0.0"
}
module "networking" {
source = "git::https://github.com/org/module.git?ref=${var.module_version}"
# Dynamic module version!
}Practical Migration Examples
Example 1: Small Project Migration
# 1. Backup existing state
terraform state pull > backup.tfstate
# 2. Install OpenTofu
brew install opentofu
# 3. Test compatibility
tofu init
tofu plan
# 4. Switch to OpenTofu
alias terraform=tofu # Optional: maintain muscle memory
# 5. Verify everything works
tofu applyExample 2: Enterprise Migration with Encryption
# 1. Generate encryption key
ENCRYPTION_KEY=$(openssl rand -base64 32)
echo "TOFU_ENCRYPTION_KEY=$ENCRYPTION_KEY" >> .env.production
# 2. Create encryption config
cat > .tofu <<EOF
encryption {
state {
method = "aes_gcm"
keys {
name = "prod_key"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
plan {
method = "aes_gcm"
keys {
name = "prod_key"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
}
EOF
# 3. Migrate with encryption
source .env.production
tofu init -migrate-state
# 4. Verify encryption
tofu state pull # State is now encrypted in backendExample 3: CI/CD Migration
# .github/workflows/terraform.yml
name: Infrastructure
on: [push, pull_request]
jobs:
opentofu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup OpenTofu
uses: opentofu/setup-opentofu@v1
with:
tofu_version: 1.8.0
- name: Init
run: tofu init
env:
TOFU_ENCRYPTION_KEY: ${{ secrets.TOFU_ENCRYPTION_KEY }}
- name: Plan
run: tofu plan
env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
TOFU_ENCRYPTION_KEY: ${{ secrets.TOFU_ENCRYPTION_KEY }}
- name: Apply
if: github.ref == 'refs/heads/main'
run: tofu apply -auto-approve
env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
TOFU_ENCRYPTION_KEY: ${{ secrets.TOFU_ENCRYPTION_KEY }}Command Compatibility
All Terraform commands work identically in OpenTofu (just replace terraform with tofu):
# Terraform # OpenTofu
terraform init → tofu init
terraform plan → tofu plan
terraform apply → tofu apply
terraform destroy → tofu destroy
terraform state → tofu state
terraform import → tofu import
terraform validate → tofu validate
terraform fmt → tofu fmt
terraform output → tofu outputCommunity and Support
OpenTofu Community:
- GitHub: https://github.com/opentofu/opentofu
- Slack: OpenTofu Workspace
- Forum: OpenTofu Discussions
- Registry: registry.opentofu.org
Terraform Community:
- Forum: HashiCorp Discuss
- GitHub: hashicorp/terraform
- Registry: registry.terraform.io
- Support: HashiCorp Support Portal
Decision Matrix
| Factor | Terraform | OpenTofu |
|---|---|---|
| License | BSL (Proprietary) | MPL 2.0 (Open Source) |
| State Encryption | Via HCP Terraform (paid) | Built-in (free) |
| Enterprise Features | HCP Terraform (Stacks, HYOK) | Community alternatives |
| Governance | HashiCorp/IBM | Linux Foundation |
| Support | Commercial support available | Community-driven |
| Innovation | HCP-focused | Community-focused |
| Cost | Free CLI, paid cloud | Completely free |
| Compatibility | Forward-compatible | Terraform 1.5.x compatible |
Recommendations
Start with OpenTofu if:
- Building new infrastructure
- No need for HCP Terraform features
- Want state encryption without cloud costs
- Prefer open-source tools
- Budget-conscious
Stay with Terraform if:
- Using HCP Terraform Stacks
- Need Sentinel policies
- Require enterprise support
- Want latest features first (1.10+)
- Established HCP investment
Easy to Switch:
- Both are viable long-term
- Migration takes < 1 hour for most projects
- State files portable
- Can evaluate both without commitment
This skill provides comprehensive OpenTofu knowledge for the terraform-expert agent.
OpenTofu 1.10 Features Reference
OCI Registry Support
Install modules from OCI (Open Container Initiative) registries:
module "networking" {
source = "oci://ghcr.io/myorg/terraform-modules/networking"
version = "1.0.0"
}Registry Configuration
# terraform.tf
terraform {
required_providers {
# OCI registry for providers
custom = {
source = "oci://registry.example.com/providers/custom"
}
}
}Native S3 Locking (No DynamoDB!)
OpenTofu 1.10+ uses native S3 locking features - no DynamoDB table required:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "terraform.tfstate"
region = "us-east-1"
# Native S3 locking - no dynamodb_table needed!
use_lockfile = true
}
}Migration from DynamoDB Locking
# 1. Backup current state
tofu state pull > backup.tfstate
# 2. Update backend config (remove dynamodb_table)
# 3. Migrate state
tofu init -migrate-stateDeprecation Warnings
Declare variables and outputs as deprecated:
variable "old_name" {
type = string
description = "Use 'new_name' instead"
deprecated = "This variable is deprecated. Use 'new_name' variable instead."
}
output "old_output" {
value = local.some_value
deprecated = "Use 'new_output' instead. Will be removed in v2.0."
}OpenTelemetry Tracing
Local observability for debugging and performance analysis:
# Enable OpenTelemetry
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_SERVICE_NAME="opentofu"
# Run with tracing
tofu planTrace Configuration
# opentofu.config
telemetry {
enabled = true
exporter = "otlp"
endpoint = "http://localhost:4317"
}Enhanced Planning Options
Target File
# Specify targets from file
echo "module.networking" > targets.txt
echo "aws_instance.web" >> targets.txt
tofu plan -target-file=targets.txtExclude File
# Exclude resources from plan
echo "aws_instance.bastion" > exclude.txt
tofu plan -exclude-file=exclude.txtGlobal Provider Cache
Safe for concurrent use with file locking:
# Enable global cache
export TF_PLUGIN_CACHE_DIR="$HOME/.tofu/plugin-cache"
# Initialize multiple workspaces concurrently
tofu -chdir=project1 init &
tofu -chdir=project2 init &
waitState Encryption Enhancements
External Key Provider
encryption {
state {
method = "external"
keys {
name = "vault_key"
command = "/usr/local/bin/vault-key-provider"
args = ["--key-id", "terraform-state"]
}
}
}PBKDF2 Key Derivation
encryption {
state {
method = "aes_gcm"
keys {
name = "derived_key"
passphrase = env.TOFU_PASSPHRASE
# Key derivation settings
pbkdf2 {
iterations = 600000
hash = "sha256"
salt = env.TOFU_SALT
}
}
}
}Compatibility
OpenTofu 1.10 maintains compatibility with:
- Terraform 1.5.x configurations
- All existing providers
- Standard module sources (registry, git, local)
- Existing state files (automatic migration)
Upgrade Path
# Check current version
tofu version
# Download 1.10
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh -s -- --version 1.10.6
# Verify
tofu version # Should show 1.10.6OpenTofu 1.11 Features Reference (Beta)
Ephemeral Resources
Work with confidential data without persisting to state:
# Ephemeral AWS Secret
ephemeral "aws_secretsmanager_secret_version" "api_key" {
secret_id = "prod/api-key"
}
# Use in resource (never stored in state)
resource "aws_lambda_function" "main" {
function_name = "my-function"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs18.x"
environment {
variables = {
API_KEY = ephemeral.aws_secretsmanager_secret_version.api_key.secret_string
}
}
}Conditional Ephemeral Resources
ephemeral "aws_secretsmanager_secret_version" "optional_secret" {
secret_id = "prod/optional-key"
lifecycle {
enabled = var.use_secrets # Only create if needed
}
}Enabled Meta-Argument
Conditional resource deployment without count hacks:
# Traditional count approach (awkward)
resource "aws_instance" "web" {
count = var.deploy_web_server ? 1 : 0
# ...
}
# OpenTofu 1.11 approach (cleaner)
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
lifecycle {
enabled = var.deploy_web_server
}
}Complex Conditions
variable "environment" {
type = string
}
variable "enable_monitoring" {
type = bool
default = true
}
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
alarm_name = "high-cpu-${var.environment}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 120
statistic = "Average"
threshold = 80
lifecycle {
# Only create in production with monitoring enabled
enabled = var.environment == "prod" && var.enable_monitoring
}
}With Modules
module "monitoring" {
source = "./modules/monitoring"
lifecycle {
enabled = var.enable_monitoring
}
}Ephemeral vs Enabled Comparison
| Feature | Ephemeral | Enabled |
|---|---|---|
| Purpose | Secret handling | Conditional creation |
| State storage | Never | Normal (when enabled) |
| Use case | Passwords, keys | Feature flags |
| Terraform equivalent | 1.10+ ephemeral | count = var ? 1 : 0 |
Combined Usage
variable "deploy_database" {
type = bool
default = true
}
variable "use_secrets_manager" {
type = bool
default = true
}
# Ephemeral secret (only when database deployed)
ephemeral "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db-password"
lifecycle {
enabled = var.deploy_database && var.use_secrets_manager
}
}
# Database (conditionally deployed)
resource "aws_db_instance" "main" {
identifier = "mydb"
engine = "postgres"
instance_class = "db.t3.micro"
password = var.use_secrets_manager ? ephemeral.aws_secretsmanager_secret_version.db_password.secret_string : var.db_password_fallback
lifecycle {
enabled = var.deploy_database
}
}Migration from Count
# Before (Terraform/OpenTofu 1.10)
resource "aws_instance" "optional" {
count = var.create_instance ? 1 : 0
ami = "ami-12345678"
instance_type = "t3.micro"
}
# After (OpenTofu 1.11)
resource "aws_instance" "optional" {
ami = "ami-12345678"
instance_type = "t3.micro"
lifecycle {
enabled = var.create_instance
}
}
# Benefits:
# - No [0] indexing needed
# - Cleaner references
# - Better error messagesInstallation
# Install beta version
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh -s -- --version 1.11.0-beta1
# Or via Homebrew
brew install opentofu --HEADCaveats (Beta)
1. Beta status: Features may change before GA 2. State format: May require migration for beta features 3. Provider support: Not all providers support ephemeral resources yet 4. Testing: Limited production testing
When to Use OpenTofu 1.11
- Need ephemeral resources for secrets (like Terraform 1.10+)
- Want cleaner conditional logic (enabled meta-argument)
- Prefer open-source alternative to Terraform
- Already using OpenTofu 1.10
OpenTofu State Encryption Reference
Overview
OpenTofu provides built-in state encryption at no cost - a feature that requires HCP Terraform (paid) in Terraform.
Basic Configuration
AES-GCM Encryption
# encryption.tf or .tofu file
encryption {
state {
method = "aes_gcm"
keys {
name = "primary_key"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
plan {
method = "aes_gcm"
keys {
name = "primary_key"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
}Generate Strong Key
# Generate 256-bit key
openssl rand -base64 32
# Output: K8x/4Xq2pR7mN1bL5tYz9wA3eI6uO0sC=
# Set environment variable
export TOFU_ENCRYPTION_KEY="K8x/4Xq2pR7mN1bL5tYz9wA3eI6uO0sC="Cloud KMS Integration
AWS KMS
encryption {
state {
method = "aws_kms"
keys {
name = "aws_key"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
}Azure Key Vault
encryption {
state {
method = "azurerm_key_vault"
keys {
name = "azure_key"
key_vault_key_id = "https://myvault.vault.azure.net/keys/tofu-state-key/version123"
}
}
}GCP KMS
encryption {
state {
method = "gcp_kms"
keys {
name = "gcp_key"
kms_crypto_key = "projects/my-project/locations/global/keyRings/tofu/cryptoKeys/state-key"
}
}
}HashiCorp Vault
encryption {
state {
method = "vault"
keys {
name = "vault_key"
vault_path = "transit/keys/tofu-state"
}
}
}Key Rotation
Manual Rotation
encryption {
state {
method = "aes_gcm"
keys {
# New key for encryption
name = "key_v2"
passphrase = env.TOFU_KEY_V2
# Old key for decryption fallback
fallback {
name = "key_v1"
passphrase = env.TOFU_KEY_V1
}
}
}
}# Set both keys
export TOFU_KEY_V1="old-key-value"
export TOFU_KEY_V2="new-key-value"
# Migrate state to new key
tofu init -migrate-state
# After successful migration, remove old key from configAutomated Rotation (KMS)
# KMS handles rotation automatically
encryption {
state {
method = "aws_kms"
keys {
name = "aws_key"
kms_key_id = "alias/tofu-state" # Use alias, AWS rotates underlying key
}
}
}Migration Scenarios
Enable Encryption on Existing State
# 1. Backup unencrypted state
tofu state pull > backup-$(date +%Y%m%d).tfstate
# 2. Add encryption config
cat >> encryption.tf <<EOF
encryption {
state {
method = "aes_gcm"
keys {
name = "primary"
passphrase = env.TOFU_ENCRYPTION_KEY
}
}
}
EOF
# 3. Set key
export TOFU_ENCRYPTION_KEY="$(openssl rand -base64 32)"
# 4. Migrate
tofu init -migrate-state
# 5. Verify
tofu state pull # Should work
cat terraform.tfstate # Should be encrypted if localDisable Encryption
# 1. Remove encryption block from config
# 2. Migrate state
tofu init -migrate-stateChange Encryption Method
# 1. Keep old method as fallback
encryption {
state {
method = "aws_kms" # New method
keys {
name = "new_key"
kms_key_id = "arn:aws:kms:..."
fallback {
method = "aes_gcm" # Old method
name = "old_key"
passphrase = env.TOFU_OLD_KEY
}
}
}
}
# 2. Migrate
tofu init -migrate-stateCI/CD Integration
GitHub Actions
- name: OpenTofu Apply
env:
TOFU_ENCRYPTION_KEY: ${{ secrets.TOFU_ENCRYPTION_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
tofu init
tofu apply -auto-approveAzure DevOps
- task: Bash@3
displayName: 'OpenTofu Apply'
env:
TOFU_ENCRYPTION_KEY: $(TOFU_ENCRYPTION_KEY)
inputs:
targetType: 'inline'
script: |
tofu init
tofu apply -auto-approveGitLab CI
apply:
image: ghcr.io/opentofu/opentofu:1.10
variables:
TOFU_ENCRYPTION_KEY: ${TOFU_ENCRYPTION_KEY}
script:
- tofu init
- tofu apply -auto-approveBest Practices
1. Never commit keys: Use environment variables or secrets management 2. Backup keys securely: Store in password manager or secrets vault 3. Use KMS in production: Cloud KMS provides automatic rotation 4. Test key rotation: Practice rotation in non-production first 5. Encrypt both state and plan: Sensitive data in both 6. Document key storage: Team should know where keys are stored
Troubleshooting
"Unable to decrypt state"
# Check key is set
echo $TOFU_ENCRYPTION_KEY
# Check key matches
# (compare hash if you stored it)
echo -n "$TOFU_ENCRYPTION_KEY" | sha256sum"State encrypted with unknown method"
# Add fallback for old method
encryption {
state {
method = "new_method"
keys {
fallback {
method = "old_method"
# ...
}
}
}
}Comparison: OpenTofu vs Terraform
| Feature | OpenTofu | Terraform |
|---|---|---|
| Built-in encryption | ✅ Free | ❌ HCP Terraform only |
| AES-GCM | ✅ | ✅ (HCP) |
| AWS KMS | ✅ | ✅ (HCP) |
| Azure Key Vault | ✅ | ✅ (HCP) |
| GCP KMS | ✅ | ✅ (HCP) |
| Key rotation | ✅ | ✅ (HCP) |
| Cost | Free | HCP pricing |