Skip to content

OpenTofu & Terraform Ecosystem - Primer

Why This Matters

In August 2023, HashiCorp changed Terraform's license from MPL 2.0 (open source) to BSL 1.1 (not open source). The BSL prohibits using Terraform to build competing products, which affected cloud providers, managed service vendors, and any company wrapping Terraform in their platform. The Linux Foundation forked Terraform 1.5.x as OpenTofu — a community-driven, genuinely open-source alternative.

Name origin: OpenTofu's name is a playful nod to the open-source community. The manifesto was originally called "OpenTF" (Open Terraform), but HashiCorp's trademark on "Terraform" forced a rename. The community voted on names and "OpenTofu" won — tofu being a versatile, open, and adaptable food, much like the project aims to be. The tofu emoji became an unofficial mascot. OpenTofu maintains full backward compatibility with Terraform HCL, state files, providers, and modules. For operations teams, this means you can switch without rewriting infrastructure code, but you need to understand the migration path, diverging features, and the broader ecosystem shift.

Core Concepts

1. OpenTofu vs Terraform — What Changed

Aspect Terraform (post-BSL) OpenTofu
License BSL 1.1 MPL 2.0 (open source)
Governance HashiCorp Linux Foundation
CLI binary terraform tofu
State format Compatible Compatible (same format)
Provider registry registry.terraform.io registry.opentofu.org (mirrors + adds)
Module registry registry.terraform.io registry.opentofu.org
HCL syntax Same Same (with additions)

OpenTofu-exclusive features (diverging from Terraform): - State encryption at rest (native, no wrapper needed) - removed block for safe resource removal from state - Provider-defined functions - OCI registry support for modules - Early variable/local evaluation

2. Installation

# Linux (Debian/Ubuntu)
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh -s -- --install-method deb

# Linux (RHEL/Fedora)
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh -s -- --install-method rpm

# macOS
brew install opentofu

# Verify
tofu version

# Docker
docker run --rm -v $(pwd):/workspace -w /workspace ghcr.io/opentofu/opentofu:latest init

3. Core Workflow: init/plan/apply

# Initialize — download providers and modules
tofu init

# Preview changes (read-only, no mutations)
tofu plan
tofu plan -out=tfplan             # save plan for exact apply
tofu plan -target=aws_instance.web  # plan specific resource

# Apply changes
tofu apply                        # interactive approval
tofu apply tfplan                 # apply saved plan (no prompt)
tofu apply -auto-approve          # skip approval (CI/CD)

# Destroy all resources
tofu destroy
tofu destroy -target=aws_instance.web  # destroy specific resource

# Format and validate
tofu fmt -recursive               # format all .tf files
tofu validate                     # syntax and reference check

4. Provider Configuration

# versions.tf
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = "~> 2.25"
    }
  }
}

# provider.tf
provider "aws" {
  region = var.aws_region
  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "opentofu"
    }
  }
}

provider "kubernetes" {
  config_path = "~/.kube/config"
  context     = var.k8s_context
}

5. State Management

Gotcha: The terraform block name in OpenTofu .tf files is NOT a mistake. OpenTofu deliberately kept terraform {} as the block name for backward compatibility. Your existing Terraform code works unchanged. This confuses newcomers who expect to see opentofu {} — but changing it would break every existing module and configuration.

State tracks the mapping between your HCL resources and real infrastructure.

# List resources in state
tofu state list

# Show a specific resource
tofu state show aws_instance.web

# Move a resource (rename without destroy/recreate)
tofu state mv aws_instance.web aws_instance.app_server

# Remove from state (resource keeps existing in cloud, Tofu stops managing it)
tofu state rm aws_instance.legacy

# Import existing infrastructure into state
tofu import aws_instance.web i-1234567890abcdef0

# Pull/push state (for state surgery)
tofu state pull > state.json
# Edit state.json carefully...
tofu state push state.json

Remote state backend (S3 example):

terraform {
  backend "s3" {
    bucket         = "myorg-tofu-state"
    key            = "production/network/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tofu-state-locks"  # state locking
    encrypt        = true
  }
}

State encryption (OpenTofu-exclusive):

terraform {
  encryption {
    key_provider "pbkdf2" "main" {
      passphrase = var.state_passphrase
    }
    method "aes_gcm" "default" {
      keys = key_provider.pbkdf2.main
    }
    state {
      method   = method.aes_gcm.default
      enforced = true
    }
    plan {
      method   = method.aes_gcm.default
      enforced = true
    }
  }
}

6. Migration from Terraform to OpenTofu

# Step 1: Check Terraform version (OpenTofu forked from 1.5.x)
terraform version
# If running Terraform <= 1.5.x, migration is straightforward

# Step 2: Install OpenTofu alongside Terraform
# (they use separate binaries, can coexist)

# Step 3: Initialize with OpenTofu (re-downloads providers from OpenTofu registry)
tofu init

# Step 4: Plan (should show no changes if state is compatible)
tofu plan
# Expected output: "No changes. Your infrastructure matches the configuration."

# Step 5: Verify by running apply
tofu apply

# Step 6: Update CI/CD pipelines to use 'tofu' instead of 'terraform'

# Step 7: Update lock file
tofu init -upgrade
# This regenerates .terraform.lock.hcl with OpenTofu registry hashes

Default trap: If you are using Terraform Cloud or Terraform Enterprise as your remote backend, migration is more involved. OpenTofu cannot authenticate to HashiCorp's proprietary backend. You must first migrate state to an S3, GCS, or Azure Blob backend before switching. Use terraform state pull > state.json followed by tofu state push state.json with the new backend configured.

Timeline: HashiCorp announced the BSL license change on August 10, 2023. The OpenTF manifesto launched on August 14, 2023, with signatures from Gruntwork, Spacelift, env0, Scalr, and others. The Linux Foundation accepted OpenTofu on September 20, 2023. OpenTofu 1.6.0 (first stable release) shipped in January 2024.

Migration gotchas: - Terraform >= 1.6 uses BSL features that OpenTofu may not replicate identically - Provider versions pinned in .terraform.lock.hcl may need tofu init -upgrade - Backend configuration is identical — no state migration needed - If using Terraform Cloud/Enterprise as backend, switch to a self-hosted or S3 backend

7. Modules and Code Organization

# modules/vpc/main.tf
variable "cidr_block" {
  type        = string
  description = "VPC CIDR block"
}

resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true
  tags = {
    Name = "${var.environment}-vpc"
  }
}

output "vpc_id" {
  value = aws_vpc.main.id
}

# Root module usage
module "vpc" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
}

# Remote module (OCI registry — OpenTofu-exclusive)
module "vpc" {
  source  = "oci://ghcr.io/myorg/modules/vpc"
  version = "1.2.0"
}

8. Operational Patterns

# Workspace management (multiple environments from same code)
tofu workspace list
tofu workspace new staging
tofu workspace select production
tofu workspace show

# Refresh state from real infrastructure
tofu apply -refresh-only

# Output values
tofu output
tofu output -json vpc_id

# Graph dependencies
tofu graph | dot -Tpng > graph.png

# Taint (force recreation on next apply)
tofu taint aws_instance.web
# Or use -replace flag:
tofu apply -replace=aws_instance.web

# Debug logging
TF_LOG=DEBUG tofu plan 2>debug.log

Quick Reference

# Core workflow
tofu init                         # download providers/modules
tofu plan                         # preview changes
tofu apply                        # apply changes
tofu destroy                      # tear down

# State operations
tofu state list                   # list managed resources
tofu state show <resource>        # show resource details
tofu import <resource> <id>       # import existing infra
tofu state mv <old> <new>         # rename resource

# Housekeeping
tofu fmt -recursive               # format code
tofu validate                     # check syntax
tofu providers                    # list required providers

# Migration from Terraform
# 1. Install tofu  2. tofu init  3. tofu plan (expect no changes)  4. Update CI

Wiki Navigation

Prerequisites