OpenTofu is an open-source infrastructure as code (IaC) tool that lets you define, provision, and manage cloud infrastructure using the same configuration language (HCL) and workflow as Terraform. It began in 2023 as a fork of Terraform 1.5.7, after HashiCorp moved Terraform to the Business Source License, and it now runs as a mature, production-ready project under the Linux Foundation.
OpenTofu is well past its first stable release. The current 1.12 line is used in production by teams who want an IaC tool with no licensing restrictions and a community-driven roadmap. It has also started to diverge from Terraform, adding capabilities the open-source Terraform binary doesn’t have, such as state encryption, early variable evaluation, and provider iteration.
This tutorial walks you through OpenTofu from the ground up: how to install it, how to migrate an existing Terraform project, the core building blocks of a configuration, and the advanced features worth knowing.
This OpenTofu tutorial includes:
- What is OpenTofu?
- Why OpenTofu matters in the DevOps ecosystem?
- How to install OpenTofu
- OpenTofu getting-started walkthrough
- How to migrate from Terraform to OpenTofu
- Basic OpenTofu components with examples
- Key features of OpenTofu
- Common problems and solutions
- OpenTofu best practices
- Advanced OpenTofu features
- Working with the OpenTofu registry
- Using OpenTofu with Spacelift
What is OpenTofu?
OpenTofu is an open-source project that serves as a fork of the legacy MPL-licensed Terraform. It was developed as a response to a change in HashiCorp’s licensing of Terraform, from Mozilla Public License (MPL) to a Business Source License (BSL), which imposed limitations on the use of Terraform for commercial purposes.
As an alternative, OpenTofu aims to offer a reliable, community-driven solution under the Linux Foundation.
Why OpenTofu matters in the DevOps ecosystem?
OpenTofu ensures that one of the core and most popular IaC tools remains open-source. With this you not only get an open-source Terraform alternative, but this also ensures the fact that the later deployment of OpenTofu will take advantage of the community’s feedback.
In the DevOps world, OpenTofu’s significance is amplified by its capabilities of infrastructure management. It continues the legacy of Terraform by supporting IaC. This enables teams to automate and efficiently manage infrastructure deployment, crucial for enhancing operational scalability and reliability.
OpenTofu’s backward compatibility with Terraform’s infrastructure also means organizations deeply integrated with Terraform can transition without disrupting their existing workflows. This compatibility, coupled with the broad support from various organizations in the DevOps community, positions OpenTofu as a potential standard for DevOps teams that rely on automation, collaboration, and open governance.
How to install OpenTofu
Let’s see different ways you can install OpenTofu.
Alpine Linux
For Alpine, you could either install OpenTofu from the Alpine Testing repository or install the apk directly.
To install OpenTofu from the repository, you can run the following command:
apk add opentofu --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing/To install the apk directly, you will first have to download one of the versions from here, and run the following command:
apk add --allow-untrusted tofu_*.apkYou can reference the official OpenTofu docs for more details here.
Ubuntu/Debian
To add the repositories, you will need to install some tooling, that should be mostly available on Debian systems:
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupgAfter this, you will need to ensure you have a copy of the OpenTofu GPG key.
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://get.opentofu.org/opentofu.gpg | sudo tee /etc/apt/keyrings/opentofu.gpg >/dev/null
curl -fsSL https://packages.opentofu.org/opentofu/tofu/gpgkey | sudo gpg --no-tty --batch --dearmor -o /etc/apt/keyrings/opentofu-repo.gpg >/dev/null
sudo chmod a+r /etc/apt/keyrings/opentofu.gpgWhen you are done with this, create the OpenTofu source list:
echo \
"deb [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main
deb-src [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main" | \
sudo tee /etc/apt/sources.list.d/opentofu.list > /dev/nullIn the end, install OpenTofu by running:
sudo apt-get update
sudo apt-get install -y tofuRHEL/SUSE/Fedora
For RPM-based systems, you will first need to create the repo:
[opentofu]
name=opentofu
baseurl=https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/\$basearch
repo_gpgcheck=0
gpgcheck=1
enabled=1
gpgkey=https://get.opentofu.org/opentofu.gpg
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
[opentofu-source]
name=opentofu-source
baseurl=https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/SRPMS
repo_gpgcheck=0
gpgcheck=1
enabled=1
gpgkey=https://get.opentofu.org/opentofu.gpg
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300If you are using yum, save this to /etc/yum.repos.d/opentofu.repo, and if you are using zipper, save this to /etc/zypp/repos.d/opentofu.repo.
After you have created the repo, run the following commands:
# yum
sudo yum install -y tofu
# zypper
zypper --gpg-auto-import-keys refresh opentofu
zypper --gpg-auto-import-keys refresh opentofu-source
zypper install -y tofuLinux (Install via Snap)
snap install --classic opentofumacOS
The OpenTofu package is available in the Homebrew Core repository so you can install it by running the following command:
brew install opentofuPortable Binary – Windows
$TOFU_VERSION="1.12.4"
$TARGET=Join-Path $env:LOCALAPPDATA OpenTofu
New-Item -ItemType Directory -Path $TARGET
Push-Location $TARGET
Invoke-WebRequest -Uri "https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_windows_amd64.zip" -OutFile "tofu_${TOFU_VERSION}_windows_amd64.zip"
Expand-Archive "tofu_${TOFU_VERSION}_windows_amd64.zip" -DestinationPath $TARGET
Remove-Item "tofu_${TOFU_VERSION}_windows_amd64.zip"
$TOFU_PATH=Join-Path $TARGET tofu.exe
Pop-Location
echo "OpenTofu is now available at ${TOFU_PATH}. Please add it to your path for easier access."Portable Binary – Linux/MacOS
#!/bin/sh
set -e
TOFU_VERSION="1.12.4"
OS="$(uname | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m | sed -e 's/aarch64/arm64/' -e 's/x86_64/amd64/')"
TEMPDIR="$(mktemp -d)"
pushd "${TEMPDIR}" >/dev/null
wget "https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_${OS}_${ARCH}.zip"
unzip "tofu_${TOFU_VERSION}_${OS}_${ARCH}.zip"
sudo mv tofu /usr/local/bin/tofu
popd >/dev/null
rm -rf "${TEMPDIR}"
echo "OpenTofu is now available at /usr/local/bin/tofu."Check the OpenTofu releases page for the current version.
OpenTofu getting-started walkthrough
The fastest way to understand OpenTofu is to provision something real. This walkthrough takes you from an empty folder to a live resource and back again, using the same loop every OpenTofu project runs on: write, init, plan, apply, and destroy.
We’ll create a single AWS Virtual Private Cloud (VPC). A bare VPC costs nothing to run, so this is safe to follow end to end.
Would you like to learn more about working with OpenTofu? Watch the video below:
Prerequisites
Before you start, make sure you have:
- OpenTofu installed (see the installation section above). Confirm it with
tofu version. - An AWS account.
- AWS credentials configured locally. OpenTofu reads them the same way the AWS CLI does, from
~/.aws/credentialsor standard environment variables such asAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY.
Step 1: Write your configuration
Create a new directory and add a single file called main.tf:
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
type = string
description = "The AWS region to deploy into"
default = "eu-west-1"
}
resource "aws_vpc" "tutorial" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "opentofu-tutorial"
ManagedBy = "OpenTofu"
}
}
output "vpc_id" {
value = aws_vpc.tutorial.id
description = "The ID of the VPC created by OpenTofu"
}This one file uses four of the building blocks covered later in this tutorial. The terraform block pins the OpenTofu version and declares the AWS provider (the required_providers block always lives inside it). The provider block configures where to deploy. The variable gives you a reusable region value, and the output returns the VPC ID once the resource exists.
Step 2: Initialize the working directory
Run tofu init in your project folder:
tofu initOpenTofu reads your configuration, downloads the AWS provider from the OpenTofu registry into a local .terraform directory, and writes a .terraform.lock.hcl dependency lock file. Commit the lock file to version control so everyone on your team resolves the same provider versions. Add the .terraform directory to your .gitignore; it’s local cache, not source.
Step 3: Format and validate
Two quick checks keep your configuration clean before you plan:
tofu fmt
tofu validatetofu fmt rewrites your files to the canonical style, and tofu validate confirms the configuration is internally consistent without contacting AWS.
Step 4: Preview the changes
tofu plantofu plan compares your configuration against current state and prints exactly what it will do, in this case create one VPC. Nothing is provisioned yet. This preview step is where you catch mistakes before they reach your cloud account, so read it every time.
Step 5: Apply
tofu apply
OpenTofu shows the same plan and prompts for confirmation. Type yes and it provisions the VPC, then prints the vpc_id output. In automated pipelines you can skip the prompt with tofu apply -auto-approve, but run it interactively while you’re learning.
Your infrastructure now exists, and OpenTofu records it in a state file (terraform.tfstate by default). Treat state as sensitive and never commit it to version control, since it can contain resource details and secrets. For team use, move it to a remote backend with locking.
Step 6: Inspect what you built
tofu state list
tofu outputtofu state list shows the resources OpenTofu is tracking, and tofu output reprints the values you exposed, so you can confirm the VPC ID without opening the AWS console.
Step 7: Destroy
When you’re done, tear everything down so you’re not tracking resources you don’t need:
tofu destroyOpenTofu lists what it will remove and asks for confirmation. Type yes, and the VPC is gone.
How to migrate from Terraform to OpenTofu
It is really easy to migrate from Terraform to OpenTofu. At the moment of writing, OpenTofu acts as a drop-in replacement for Terraform.
What you need to do to perform the migration is simply install OpenTofu using one of the methods mentioned above that works for your operating system, and after that run:
tofu init -upgradeThis will ensure that you will use the OpenTofu registry for your OpenTofu providers.
After that, all of the Terraform commands that you love and use can be performed using the tofu binary. See: OpenTofu Cheat Sheet: 31 CLI Commands You Should Know
Basic OpenTofu components with examples
As OpenTofu began as a fork of Terraform 1.5.7, it shares the same core components and configuration language.
Provider
OpenTofu providers are essentially plugins enabling OpenTofu to interact with various infrastructure resources. They serve as a bridge, translating OpenTofu configurations into corresponding API calls, enabling OpenTofu to effectively manage a diverse range of resources across different environments. These providers come equipped with their own unique resources and data sources, which can be managed and set up through OpenTofu.
A common misconception about OpenTofu providers is that they are exclusive to cloud service providers like Amazon Web Services (AWS), Microsoft Azure, Google Cloud Platform (GCP), or Oracle Cloud Infrastructure (OCI). However, this is not the case. OpenTofu providers extend beyond these cloud vendors to include others such as Template, Kubernetes, Helm, Spacelift, Artifactory, vSphere, and Aviatrix.
Each provider is tailored with its specific resources and data sources. For instance, the AWS provider includes resources to handle EC2 instances, EBS volumes, and ELB load balancers, all manageable via OpenTofu.
Example Oracle Cloud Provider
provider "oci" {
tenancy_ocid = "tenancy_ocid"
user_ocid = "user_ocid"
fingerprint = "fingerprint"
private_key_path = "private_key_path"
region = "region"
}Resource
Resources in OpenTofu refer to the infrastructure elements that OpenTofu can manage. This includes, but is not limited to, virtual machines, virtual networks, DNS entries, and pods. Each of these resources is defined by a specific type, such as “aws_instance,” “google_dns_record,” “kubernetes_pod,” or “oci_core_vcn.” Additionally, these resources come with a range of configurable properties like instance size or VCN CIDR.
OpenTofu facilitates creating, updating, and deleting these resources. It automatically manages the dependencies among them to ensure they are established in the appropriate sequence. Furthermore, if desired, you can introduce explicit dependencies between certain resources using the “depends_on” feature. This capability underscores OpenTofu’s flexibility and control in handling complex infrastructure setups.
Example resource
resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
}Datasource
A data source in OpenTofu is a configuration element designed to fetch data from an external source. This data can then be utilized as a parameter within resources during their provisioning. When we mention external sources, we are encompassing a variety of origins such as infrastructure set up manually, and resources established through other OpenTofu configurations, among others.
These data sources are specified within the providers relevant to OpenTofu and are referenced using a distinct block called data. The structure and documentation for a data source closely resemble those of a resource. Therefore, if you’re already proficient with resource configuration in OpenTofu, adapting to using data sources should be straightforward.
For example, consider a data source that retrieves information about an existing AWS VPC (Virtual Private Cloud). You can define this data source in OpenTofu to gather details such as the VPC’s ID or CIDR block, which can then be used to configure related AWS resources like subnets or security groups within your OpenTofu configuration. This allows for seamless integration and management of both existing and new infrastructure components.
Example Datasource
data "aws_vpc" "example" {
id = "vpc-id"
}
resource "aws_subnet" "example" {
vpc_id = data.aws_vpc.example.id
cidr_block = "10.0.1.0/24"
…
}Output
An output in OpenTofu is a feature that enables you to conveniently view the value of a specific data source, resource, local, or variable after OpenTofu has executed changes to the infrastructure. This output is defined in the OpenTofu configuration file and can be accessed using the tofu output command, but only after a tofu apply has been executed.. Additionally, outputs can be employed to share various resources within a module.
Outputs don’t rely on any specific provider – they are a unique type of block that operates independently. The key parameters that an output supports include value, where you define what you want to display, and description (which is optional), where you explain the output’s purpose.
Example Output
output "vpc_id" {
value = aws_vpc.example.id
description = "The VPC ID"
}Variable
OpenTofu variables provide a way to parameterize configuration files, thereby enhancing their flexibility and reusability. These variables are declared in OpenTofu configurations and can be used to input diverse values without modifying the core configuration. For instance, a variable might be used to define the size of a virtual machine, the name of a resource, or the region in which a resource is deployed.
As in all programming languages, when we are defining a variable, we have to assign it a type, or in some cases, it automatically infers a type.
Supported variable types in OpenTofu:
- Primitive
– String
– Number
– Bool
- Complex
– List
– Set
– Map
– Object
- Null – Represents absence
There is also the any type, which represents any type of resource, but its usage is not recommended as it will make your code harder to maintain.
Example variables
variable "cidr_block" {
type = string
description = "The VPC CIDR block"
default = "10.0.0.0/16"
}
variable "vpc_details" {
type = map(string)
description = "The VPC details"
default = {
"name" = "vpc1"
"cidr" = "10.0.0.0/16"
}
}Local
Local variables in OpenTofu are a convenient way of assigning a value to an expression. They are defined into a locals block, and are referenced by prefixing them with a “local”.
Expanding on this, locals in OpenTofu act as an intermediary layer in your configurations, helping to simplify and clarify expressions. They can be particularly useful for reducing repetition and making your code more readable and maintainable. This capability to define and manipulate a wide range of attributes and operations through locals is essential for crafting efficient and streamlined infrastructure code. It keeps OpenTofu configurations organized and easy to maintain as they grow.
Example Local
locals {
cidr_block = "10.0.0.0/16"
name = "vpc1"
}Provisioners
OpenTofu provisioners are distinct from providers and serve a different purpose. They allow you to execute various commands or scripts either on your local machine or on a remote machine. Additionally, they enable the transfer of files from your local machine to a remote one. These provisioners are contained within a resource, meaning that to utilize one, you simply need to insert a provisioner block into the specific resource in your OpenTofu configuration.
Provisioners are generally viewed as a measure of last resort within OpenTofu, as they step outside the tool’s declarative model. This model typically focuses on defining the desired end-state of infrastructure, whereas provisioners perform imperative actions.
OpenTofu includes three main types of provisioners:
- local-exec: Executes commands on the local machine.
- file: Used for file transfers, typically in conjunction with a connection block for specifying details of the remote machine.
- remote-exec: Executes commands on a remote machine, also usually used with a connection block for remote access details.
Example provisioner
resource "null_resource" "this" {
provisioner "local-exec" {
command = "echo Hello World!"
}
}Key features of OpenTofu
OpenTofu is a valuable tool in the IaC world. It brings several features designed to offer flexibility, efficiency, and a community-driven approach to infrastructure management.
Open source and community-driven
One of the foundational aspects of OpenTofu is its open-source nature, ensuring it is freely available and easy to change. This fosters a community-driven development process, where contributions and improvements are sourced from a diverse group of users and organizations. Having OpenTofu under the Linux Foundation umbrella brings several benefits to the product’s well-being and ensures steady development.
Provider ecosystem
Similar to Terraform, OpenTofu works with a range of providers that extend its functionality to different technologies and services, including major cloud platforms, databases, and more.
Execution plans
Once the infrastructure is defined by the user, OpenTofu generates an execution plan. This plan outlines the actions that OpenTofu will undertake and seeks the user’s confirmation before proceeding with any modifications to the infrastructure. This critical step allows for a thorough review of the proposed changes, providing an opportunity to confirm or adjust them before OpenTofu executes any operations, such as creation, modification, or deletion of infrastructure components.
State management
OpenTofu keeps track of the state of managed infrastructure, ensuring that changes are applied incrementally and consistently. This state management is key to understanding current and past configurations and to planning future changes.
Modular design
Giving the ability to organize code as modules promotes reusability, and maintainability, making OpenTofu manage large scale and complex infrastructure easier.
Common problems and solutions
As with Terraform, you will face some common challenges with OpenTofu, too.
| Problem Name | Problem Description | Solution |
| State file issues | OpenTofu state file, which tracks infrastructure state, can become out of sync or corrupted | Regularly back up your state file. Utilize OpenTofu’s state management commands for troubleshooting. Consider a remote state backend with state locking for team use |
| Complex dependency management | Managing complex dependencies in OpenTofu, especially implicit ones, can be challenging | Explicitly define dependencies with depends_on. Break down large configurations into smaller modules to manage dependencies more effectively |
| Version incompatibility | Incompatibilities can occur when using different versions of OpenTofu or provider versions | Standardize a specific version of OpenTofu across your team. Pin specific versions of providers to ensure consistency. |
| Configuration drift | The actual infrastructure state may drift from what’s defined in OpenTofu configurations | Frequently run tofu plan to detect drifts. Apply changes as needed and implement policies to restrict manual changes to infrastructure. Use OpenTofu with Spacelift and do advanced drift detection. |
| Multi-env management | Managing OpenTofu configurations across multiple environments (development, staging, production) can be complex | Use separate state files or OpenTofu workspaces for each environment. Parameterize configurations with variables for different environments. Use OpenTofu with Spacelift to facilitate your multi-env management by using different stacks and implementing stack dependencies. |
OpenTofu best practices
Being the successor of Terraform, OpenTofu inherits many best practices from it, such as:
- Limit manual changes – Once you use IaC, you should ensure that you limit manual changes, as these changes may incur costly drifts in the future
- Modularize your code – Make your code DRY, ensure you can reuse it and maintain a clean an organized structure for your OpenTofu projects
- Use remote state – Take advantage of remote state to enhance collaboration between your team members and ensure that there are no parallel operation happening on the same configuration
- Use version control – This helps with ensuring that you use a vcs for the source of truth of your code and simplifies tracking changes, collaboration, and reverting to an older version if required
- Use variables and outputs – Parametrize your configuration to promote reusability and flexibility. By also leveraging outputs, you can share information about your resources, and even export configurations from modules
- Take advantage of loops and conditional – Loops and conditional ensure your code can encompass multiple use cases, and enhance reusability
Advanced OpenTofu features
Beyond the basics, OpenTofu includes a set of features for more complex workflows, some inherited from Terraform and some, like state encryption and dynamic prevent_destroy, unique to OpenTofu.
Import block
With OpenTofu, importing existing resources is easier than ever. OpenTofu supports importing existing resources through an import block, which generates configuration for you.
The import block requires two parameters: the ID of the resource to be imported and the HCL address for the new resource block. Here’s a simplified example for an Amazon VPC:
import {
id = "vpc-abcd1234" # Resource ID in the cloud
to = aws_vpc.example # Resource address in OpenTofu configuration
}After adding the import block, you can execute a plan for generating the code for the resource. This is done by using:
tofu plan -generate-config-out=generated_resources.tfThis generated code can be reviewed and adjusted if necessary, followed by a standard apply operation to complete the import into the state.
Loops and conditionals
Loops and conditional statements are a crucial feature of OpenTofu. Without this capability, OpenTofu, wouldn’t be one of the best solutions on the market for IaC management.
There are essentially two types of loops you can use directly on a resource:
- for_each
- count
Both achieve the same behavior, but essentially, for_each is looping on a set or a map, and count creates the same resource multiple times, but using a number.
If you are using count on a variable list, you will face a potential problem: if the list has many elements and you decide, for whatever reason, do delete an element from the middle of the list, every other resource from the middle to the end will get recreated because it changes its index. This is something that can be easily avoided with for_each, as you won’t face any issues with indexes
Example for_each
locals {
vpcs = {
vpc1 = {
cidr = "10.0.0.0/16"
}
vpc2 = {
cidr = "11.0.0.0/16"
}
}
}
resource "aws_vpc" "this" {
for_each = local.vpcs
cidr_block = each.value.cidr
}Example count
resource "aws_vpc" "count_vpc" {
count = 3
cidr_block = "10.0.0.0/16"
}You can also use for loops when you are building different expressions and you also have the possibility of using if blocks inside of them:
locals {
list_of_even_numbers = [for i in [1, 2, 3, 4, 5, 6] : i if i % 2 == 0]
}In the above expression, we are cycling through a list of numbers using a for loop, and we are building a list with all of the even numbers by checking if the remainder of the division by two is equal to 0.
Ternary operators are also available in OpenTofu:
locals {
enable_vpc = true
}
resource "aws_vpc" "this" {
count = local.enable_vpc ? 1 : 0
cidr_block = "10.0.0.0/16"
}In the above example, we are using a local boolean variable “enable_vpc” and based on it, we will conditionally create vpcs. We are checking if “enable_vpc” is equal to true, and if it is we will create 1 VPC, otherwise we won’t create any vpcs.
Sensitive and nonsensitive
In OpenTofu, the concepts of sensitive and nonsensitive data are used to handle the exposure of potentially confidential or sensitive information in outputs or logs.
You have the option to either use the sensitive parameter on a variable or an output, and you even have two functions available that will mark your data as sensitive or nonsensitive.
variable "password" {
description = "Log in password"
type = string
sensitive = true
}The sensitive function takes any value and marks it as sensitive. OpenTofu then treats this value with confidentiality, ensuring it does not appear in logs or output. It is particularly useful when a sensitive value arises from within your module, such as data loaded from a file:
locals {
sensitive_content = sensitive(file("${path.module}/sensitive.txt"))
}The nonsensitive function takes a sensitive value and returns a copy of it with the sensitive marking removed. This is useful when you’ve derived a non-sensitive result from a sensitive value and wish to expose it in your output. However, caution is advised since using this function can expose sensitive values if not used appropriately:
output "sensitive_example_hash" {
value = nonsensitive(sha256(var.sensitive_example))
}Testing capabilities
The tofu test command in OpenTofu is designed to facilitate integration testing for your OpenTofu configurations.
How it works in a nutshell:
- Create a testing suite – Defining a test suite through reading *.tftest.hcl files. These files contain the test definitions
- Reading Global Input Variables – It reads all global input variables applicable to the entire test suite
- Execution Order – The test files are sorted alphabetically to establish an execution order
- Executing Test File – Each test file in the suite is executed sequentially. Within each test file:
- The run configurations are prepared and validated
- The tofu plan+apply or tofu apply operations are performed
- It checks for expected failures
- Assertion conditions are verified
- The suite status is updated after each run
- Summary and Cleanup – After executing the tests, OpenTofu prints out summaries for each test file and for each run. It then attempts to destroy the resources provisioned by each run in reverse order, ensuring clean-up of the testing infrastructure.
- Termination Status – The command exits with a status code 0 if all tests passed or 1 if any tests failed
Read more on how to define tests here.
State encryption
Terraform’s open-source binary writes state in plaintext, so anyone who can read your backend can read your secrets. OpenTofu, since version 1.7, encrypts the entire state (and optionally your plan files) at rest, using a key provider you control: a PBKDF2 passphrase, AWS KMS, GCP KMS, or OpenBao.
You configure it with an encryption block inside the terraform block:
terraform {
encryption {
key_provider "pbkdf2" "main" {
passphrase = var.state_passphrase
}
method "aes_gcm" "main" {
keys = key_provider.pbkdf2.main
}
state {
method = method.aes_gcm.main
enforced = true
}
}
}With enforced = true, OpenTofu refuses to read or write unencrypted state, so nobody can quietly drop the encryption later. When you first encrypt an existing project, add a temporary unencrypted fallback method so OpenTofu can read the old state on the migration run, then remove it.
For a deeper walkthrough, see OpenTofu state encryption.
Provider iteration with for_each
Managing one provider per region or account used to mean copying the same provider block over and over. OpenTofu 1.9 lets you drive aliased provider instances from a variable instead:
variable "regions" {
type = set(string)
default = ["eu-west-1", "us-east-1"]
}
variable "disabled_regions" {
type = set(string)
default = []
}
provider "aws" {
alias = "by_region"
for_each = var.regions
region = each.key
}
resource "aws_vpc" "regional" {
for_each = setsubtract(var.regions, var.disabled_regions)
provider = aws.by_region[each.key]
cidr_block = "10.0.0.0/16"
}The provider block must have a static alias, and the for_each value has to be known statically (variables and locals only, not data sources).
One caveat worth respecting: the resource’s for_each must not be the exact same expression as the provider’s, so the provider instance outlives its resources during destroy — that’s why the resources here iterate over a derived set. To drop a region cleanly, add it to disabled_regions and apply (which destroys its resources while the provider still exists), then remove it from regions.
Dynamic prevent_destroy
Terraform’s prevent_destroy accepts only a hardcoded boolean, so a module that protects a production database can’t be reused in a development environment where you want to tear things down freely. Teams worked around this by forking modules or skipping the protection entirely. OpenTofu 1.12 lets prevent_destroy reference any expression in the same module:
variable "is_production" {
type = bool
default = false
}
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = var.is_production
}
}Set is_production = true in your production workspace and leave it false everywhere else. One module covers both environments.
Working with the OpenTofu registry
OpenTofu has its own registry for providers and modules. The provider registry protocol and the module registry protocol are systems used by OpenTofu to discover details and install providers and modules.
Providers are uniquely identified by addresses formatted as hostname/namespace/type. This protocol supports versioning, where each provider address is associated with multiple versions following Semantic Versioning conventions.
Service discovery begins with the OpenTofu CLI querying a hostname to locate providers, which is essential for ensuring compatibility with specific OpenTofu versions and platforms. The protocol also outlines the necessary steps for listing available versions of a provider and retrieving provider packages, including download URLs and necessary metadata like checksums and signing keys, so make sure to check it out to view some usage examples.
Modules, on the other hand, are identified by a specific address format (hostname/namespace/name/system), with each address containing multiple versions following Semantic Versioning. The protocol includes steps for listing available module versions and retrieving specific module versions for download as in the provider case.
You will also need to check the documentation to view some usage examples. It also provides a service discovery mechanism, where the CLI uses a hostname to locate modules. This approach facilitates the management and distribution of modules in a structured and efficient manner.
Using OpenTofu with Spacelift
Spacelift is an infrastructure orchestration platform that supports both OpenTofu and Terraform, as well as other tools such as Pulumi, CloudFormation, Terragrunt, Ansible, and Kubernetes. Spacelift offers a variety of features that map easily to your OpenTofu and Terraform workflow.
Spacelift stacks enable you to plug in the VCS repository containing your Terraform and OpenTofu configuration files and run a GitOps workflow for them.
At the stack level, you can add a variety of other components that will influence this GitOps workflow, such as:
- Policies control the kind of resources engineers can create, their parameters, the number of approvals you need for runs, where to send notifications, and more.
- Stack dependencies allow you to build dependencies between your configurations and even share outputs between them. There are no constraints on creating dependencies between multiple tools or the number of dependencies you can have.
- Cloud integrations are dynamic credentials for major cloud providers (AWS, Microsoft Azure, Google Cloud).
- Contexts are shareable containers for your environment variables, mounted files, and lifecycle hooks.
- Drift detection allows you to detect infrastructure drift easily and, optionally, remediate it.
- Resources view provides enhanced observability of all resources deployed with your Spacelift account.
- Spacelift Intelligence: AI capabilities are embedded in the platform, such as Infra Assistant and Intent, which operate within the same policies and approval flows as everything else, so that teams can adopt AI-driven workflows with the guardrails already in place.
For teams that want to move even faster on non-critical workloads, Spacelift Intent lets developers provision infrastructure using natural language, without writing any infrastructure as code, while the same policies, guardrails, and audit trails apply.
If you need help migrating from Terraform to OpenTofu or are looking for an infrastructure orchestration platform that supports the top flavors of infrastructure as code, configuration management, and container orchestration, contact Spacelift.
As a founding partner of the initiative, Spacelift offers the native and commercial support you need to ensure your OpenTofu success. Learn more about OpenTofu Commercial Support & Services.
In under a year, Völur has progressed from using ClickOps to manage its resources to having about 1,500 resources in Spacelift state today. They are also one of the early adopters of OpenTofu, the open-source alternative to Terraform. The first stack migration took a couple of hours and proceeded without any issues. “We’re still in the migration process, but it has been so smooth that most of the time we spend on code and plan reviews, not on Spacelift applying changes or performing its tasks.”
Key points
OpenTofu is a mature, production-ready project. Installing it is quick, and migrating from Terraform is straightforward. You can easily install it and the migration from Terraform is pretty straightforward. With OpenTofu you get all the advantages that Terraform offers, and judging by the fact that it is open source and under the Linux Foundation umbrella, you get what Terraform never had – a community-first development approach.
Terraform and OpenTofu have already diverged. OpenTofu now ships capabilities the open-source Terraform binary doesn’t have, including state encryption (1.7), early variable evaluation (1.8), provider iteration with for_each and the -exclude flag (1.9), OCI registry support (1.10), ephemeral values (1.11), and dynamic prevent_destroy (1.12). Both tools still share the same core language and workflow, so the choice comes down to licensing, roadmap, and which feature set fits your team.
If you want to take your OpenTofu workflow to the next level, you can use Spacelift. Create a free account today or book a demo with one of our engineers.
OpenTofu commercial support
Spacelift offers native and commercial support to ensure your OpenTofu success. If you need a reliable partner to run your critical workloads with OpenTofu, accelerate your migration, provide support coverage, or train your team – we are here to help.
Frequently asked questions
Is OpenTofu free to use?
Yes. OpenTofu is fully open source under the Mozilla Public License 2.0 (MPL 2.0), with no restrictions on commercial or personal use. It is maintained under the Linux Foundation, so its roadmap is community-driven rather than controlled by a single vendor.
Is OpenTofu a drop-in replacement for Terraform?
For most projects, yes. OpenTofu uses the same configuration language (HCL), the same commands, and the same state format, so you swap the terraform binary for tofu and keep working. On the first apply, OpenTofu updates the terraform_version marker inside your state file, but your configuration and workflow stay the same.
What can OpenTofu do that Terraform can't?
OpenTofu has diverged from Terraform and now ships capabilities the open-source Terraform binary lacks. These include built-in state encryption (1.7), early variable evaluation (1.8), provider iteration with for_each and the -exclude plan flag (1.9), OCI registry support (1.10), ephemeral values (1.11), and dynamic prevent_destroy (1.12). The two tools still share the same core language, so these features are additive rather than breaking.

