OpenTofu: A Beginners Guide to a Terraform Fork Including Migration From Terraform
Working with OpenTofu
Demo Count and for each
In this lab, you’ll learn how to leverage the count
and for_each
meta-arguments to create multiple resource instances dynamically in OpenTofu. We’ll work through a series of tasks:
- Inspecting a basic configuration
- Scaling with
count
- Parameterizing with variables
- Ensuring uniqueness with
for_each
Task 1: Inspect the Base Configuration
Navigate to your project directory:
cd /root/opentofu-projects/project-shade
Open the default main.tf
:
resource "local_sensitive_file" "name" {
filename = "/root/user-data"
content = "password: S3cr3tP@ssw0rd"
}
Since there’s only one resource block, running opentofu plan
would create one file at /root/user-data
.
Task 2: Create Multiple Instances with count
Add the count
argument to generate three instances:
resource "local_sensitive_file" "name" {
filename = "/root/user-data"
content = "password: S3cr3tP@ssw0rd"
count = 3
}
Initialize and preview:
opentofu init
opentofu plan
Expected plan excerpt:
# local_sensitive_file.name will be created with 3 instances
+ resource "local_sensitive_file" "name" {
+ count = 3
+ filename = "/root/user-data"
+ content = (sensitive)
}
Apply the changes:
opentofu apply
All three instances target the same filepath, so you end up with just one actual file on disk.
Note
Although Terraform plans three resources, they all write to /root/user-data
. Use unique filenames or a loop index to avoid overwriting.
Task 3: Accessing Resources by Index
Resources managed with count
form a list. To view the ID of the second element (index 1):
opentofu state show local_sensitive_file.name[1]
Look for the id
attribute in the output.
Task 4: Parameterize with Variables and count
Define variables in variables.tf
:
variable "users" {
type = list(string)
}
variable "content" {
default = "password: S3Cr3tP@ssw0rd"
}
Update main.tf
:
resource "local_sensitive_file" "name" {
count = length(var.users)
filename = var.users[count.index]
content = var.content
}
Now each users
element becomes a filename. Initialize and apply:
opentofu init
opentofu plan
opentofu apply
Task 5: Set Default Values for Variables
Add sensible defaults in variables.tf
:
variable "users" {
type = list(string)
default = ["/root/user1", "/root/user11", "/root/user12"]
}
variable "content" {
default = "password: S3Cr3tP@ssw0rd"
}
Key points:
- Type of
users
:list(string)
- List vs. set: Lists allow duplicates; sets do not.
Example of a duplicate in a list (invalid for a set):
variable "users" {
default = [
"/root/user10",
"/root/user1",
"/root/user12",
"/root/user10" # duplicate
]
}
Task 6: Ensure Unique Instances with for_each
Refactor main.tf
to use for_each
on a set:
resource "local_sensitive_file" "name" {
for_each = toset(var.users)
filename = each.value
content = var.content
}
The toset()
function removes duplicates, and for_each
creates a map keyed by each unique filename.
Initialize and apply:
opentofu init
opentofu plan
opentofu apply
Expected output:
local_sensitive_file.name["/root/user10"]: Creating...
local_sensitive_file.name["/root/user11"]: Creating...
local_sensitive_file.name["/root/user12"]: Creating...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Why use `for_each`?
- Eliminates duplicates automatically
- Creates a map, so you can reference resources by key:
local_sensitive_file.name["/root/user11"]
Comparing count
vs. for_each
Feature | count | for_each |
---|---|---|
Data structure | List (indexed) | Map (keyed by value) |
Handling duplicates | Requires manual deduplication | Automatic when using toset() |
Reference syntax | resource.name[0] | resource.name["key"] |
Q&A
What data structure does
for_each
produce?
A map, keyed by each unique element.How do you address the resource for
/root/user11
withfor_each
?local_sensitive_file.name["/root/user11"]
Further Reading
That’s a wrap for this lab. Happy automating!
Watch Video
Watch video content
Practice Lab
Practice lab