OpenTofu: A Beginners Guide to a Terraform Fork Including Migration From Terraform

OpenTofu Basics

Understanding the Variable Block

In this lesson, we’ll explore how to declare and manage input variables in your OpenTofu configuration. A variable block can be empty—if you omit arguments, you must provide its value at runtime (for example, with tofu apply --var 'name=value'). You can also specify defaults, descriptions, types, sensitivity flags, and validation rules to enforce best practices and improve readability.

Note

If you don’t set a default, OpenTofu will require the variable at apply time. Use --var or environment variables to supply missing values.


Defining Defaults and Descriptions

Provide fallback values and document their purpose with the default and description arguments:

variable "ami" {
  default     = "ami-0edab43b6fa892279"
  description = "EC2 machine image ID"
}

variable "instance_type" {
  default     = "t2.micro"
  description = "EC2 instance size"
}

Specifying a Type

By default, variables accept any value. Enforce strict typing using the type argument:

variable "ami" {
  description = "EC2 machine image ID"
  type        = string
  default     = "ami-0edab43b6fa892279"
}

variable "instance_type" {
  description = "EC2 instance size"
  type        = string
  default     = "t2.micro"
}

Marking Variables as Sensitive

Set sensitive = true to hide variable values in plan/apply outputs (the state file still records them). The default is false.

variable "ami" {
  description = "EC2 machine image ID"
  type        = string
  default     = "ami-0edab43b6fa892279"
  sensitive   = true
}

variable "instance_type" {
  description = "EC2 instance size"
  type        = string
  default     = "t2.micro"
  sensitive   = false
}

Warning

Sensitive variables will not be displayed in CLI output, but they are stored in the state file. Protect your state accordingly.


Input Validation

Use a validation block to enforce naming conventions or other business rules:

variable "ami" {
  description = "The ID of the machine image (AMI) to use for the server."
  type        = string

  validation {
    condition     = substr(var.ami, 0, 4) == "ami-"
    error_message = "The AMI must start with \"ami-\"."
  }
}

Trying an invalid AMI:

$ tofu apply --var 'ami=abc-11223'
Error: Invalid value for variable

  on main.tf line 1:
   1: variable "ami" {
  
  The AMI must start with "ami-".
  This was checked by the validation rule at main.tf:5,3-13.

Simple Types

OpenTofu supports these basic types: string, number, bool, and (by default) any. OpenTofu will attempt conversion between string↔number and string↔bool, but other mismatches cause errors.

Type Conversion

OpenTofu converts "2"2 and "true"true. Invalid conversions (e.g., "abc" → number) will fail during planning.

TypeExample DefaultDescription
string"ami-0edab43b6fa"Text values
number2Numeric values
booltrueBoolean flags
anyomitted or nullAny type, no checks
variable "count" {
  default     = 2
  type        = number
  description = "Number of VMs to create"
}

variable "monitoring" {
  default     = true
  type        = bool
  description = "Enable detailed monitoring"
}

Collection Types

OpenTofu also supports composite types: list, map, set, object, and tuple. Use them to model complex inputs.

CollectionDescriptionSyntax
listOrdered sequence of elementslist(string)
mapKey-value pairs with uniform value typesmap(string)
setUnordered unique elements (no duplicates)set(string)
objectStructured attributes with named typesobject({ ... })
tupleFixed-length heterogeneous sequencetuple([type, ...])

List

variable "servers" {
  type    = list(string)
  default = ["web1", "web2", "web3"]
}

Access by index:

resource "aws_instance" "web" {
  ami           = var.ami
  instance_type = var.instance_type
  tags = {
    name = var.servers[0]  # "web1"
  }
}

Map

variable "instance_type" {
  type = map(string)
  default = {
    production  = "m5.large"
    development = "t2.micro"
  }
}

resource "aws_instance" "dev" {
  ami           = var.ami
  instance_type = var.instance_type["development"]
  tags = {
    name = var.servers[0]
  }
}

Set

variable "prefixes" {
  type    = set(string)
  default = ["web1", "web2", "web3"]
}

variable "prefix_dup_error" {
  type    = set(string)
  default = ["web1", "web2", "web1"]  # Error: duplicate element
}

Object

variable "bella" {
  type = object({
    name         = string
    color        = string
    age          = number
    food         = list(string)
    favorite_pet = bool
  })
  default = {
    name         = "bella"
    color        = "brown"
    age          = 7
    food         = ["fish", "chicken", "turkey"]
    favorite_pet = true
  }
}

Tuple

variable "web" {
  type    = tuple([string, number, bool])
  default = ["web1", 7, true]
}

variable "db" {
  type    = tuple([string, number, bool])
  default = ["db1", 1, true, "db2"]  # ERROR: expects exactly 3 elements
}

Watch Video

Watch video content

Previous
Using Input Variables