Terraform variables accept values from a module’s caller; locals name expressions for reuse inside a module; count and for_each create repeated instances; dynamic blocks generate nested provider-defined blocks; and outputs expose selected values to callers or the CLI. The key choice is whether repeated instances are best identified by a number or by meaningful keys.
How values move through a Terraform module
A reusable module has an input interface, internal expressions, and outputs. A caller supplies values to input variables. The module can transform or reuse those values with expressions and locals, then pass them to resources and data sources. Outputs expose selected results to the caller, Terraform CLI, or HCP Terraform. HashiCorp explains this module value flow in its Manage values in modules documentation.
As an Amazon Associate I earn from qualifying purchases.
Terraform uses the word “variable” specifically for an input declared in a variable block. Names such as each.key, each.value, and count.index are contextual values available within particular repeated blocks; they are not module inputs.
How Terraform input variables work
Input variables let module consumers customize a module without editing its source. They can be declared in root or child modules. A parent module passes a value to a child through its module block. Root-module inputs can be set through CLI options, environment variables, variable definition files, or HCP Terraform. See HashiCorp’s variable block reference for the supported mechanisms and version-specific details.
#1 Best Overall
Declare an input
variable "service_name" {
type = string
description = "Name used to identify the service"
default = "example-service"
}
Without a default, a caller must provide a value. The type constrains its expected shape, while description records its purpose. Add a validation block when a value must satisfy rules beyond its type. The nullable argument controls whether null can be assigned.
A variable marked sensitive is hidden from ordinary CLI output, but that setting alone does not prevent the value from being stored in state or plan files. The variable reference also documents ephemeral and const; check that reference for their semantics in the Terraform version your project pins.
Pass a value to a child module
module "service" {
source = "./modules/service"
service_name = var.service_name
}
Here the root module forwards its input to the child module’s input of the same name. HashiCorp’s style guide recommends a consistent variable argument order: type, description, optional default, optional sensitive, then validation. Use descriptive names and explain the expected value rather than relying on a name alone; see the Terraform configuration style guide.
Use locals to name reusable expressions
A local value is an expression named inside a module, not a value supplied by the caller. Locals can refer to variables, resources, data sources, and other locals. They are useful when a calculated value is needed in multiple places or when a long expression deserves a meaningful name. HashiCorp describes locals in its locals block reference.
locals {
service_name_normalized = lower(trimspace(var.service_name))
}
Use local.service_name_normalized where that expression is needed. A local does not create a new input or change the underlying value; it gives an expression a reusable name.
Choose count or for_each by instance identity
Both count and for_each repeat resource or module instances, but they give each instance a different identity and address shape. Use count when the instances are nearly identical and a numeric index is a natural fit. Use for_each when each instance has a meaningful identity represented by a map key or set member. A single block cannot use both. These distinctions are covered in HashiCorp’s count and for_each references.
| Construct | What it repeats | Identity and access | Best fit | Main constraint |
|---|---|---|---|---|
count |
Resource, module, and other documented block instances | Numeric index; use count.index |
Nearly identical instances where a number represents them well | Cannot be used in the same block as for_each |
for_each |
Resource, module, data, and other documented block instances | Map key or set member; use each.key and each.value |
Instances with meaningful identities or distinct values | Keys or members must be known before apply; sensitive values are disallowed |
dynamic |
Nested configuration blocks | Iterator object with key and value |
Generating provider-defined nested blocks from a collection | Cannot generate Terraform meta-argument blocks |
Use count for indexed instances
resource "example_resource" "worker" {
count = 2
name = "worker-${count.index}"
}
This produces two instances addressed by numeric index. A counted resource or module has a list-like collection shape when referenced, so an individual instance is selected by index. The resource arguments in this example are illustrative: the valid resource type and arguments depend on the provider schema.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use for_each when keys carry meaning
variable "workers" {
type = map(string)
}
resource "example_resource" "worker" {
for_each = var.workers
name = each.key
size = each.value
}
For a map, each map key identifies an instance and each.value provides its associated value. A resource or module using for_each has a map-like collection shape when referenced, keyed by those identities. Using stable, understandable keys is a practical way to make instance identity clear in configuration and addresses.
For resources and modules, for_each accepts a map or a set of strings. Its keys or set members must be known before Terraform performs the apply; they cannot depend on impure functions such as uuid, bcrypt, or timestamp. Sensitive values are not allowed as the argument because instance identities are disclosed in Terraform’s UI. Data-source instances can also be addressed separately with count or for_each; see HashiCorp’s data source documentation.
Rank #4
What dynamic blocks do—and do not do
A dynamic block generates nested configuration blocks inside a resource, data source, provider, or provisioner block. It is not another way to create repeated resource instances. Its label names the nested block type, its for_each supplies the elements, and content defines each generated block’s body.
dynamic "setting" {
for_each = var.settings
iterator = item
content {
name = item.key
value = item.value
}
}
The optional iterator changes the temporary iterator’s name; without it, the iterator defaults to the dynamic block label. The iterator exposes key and value. The nested block label and its arguments must be supported by the containing provider-defined schema, so this pattern is illustrative rather than a complete resource for a particular provider.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteDynamic blocks can generate only nested blocks belonging to the configured construct. They cannot generate Terraform meta-argument blocks such as lifecycle or provisioner, which Terraform processes earlier. HashiCorp advises using literal nested blocks when possible and avoiding dynamic blocks when they make configuration harder to read; see Dynamic Blocks.
Expose selected values with outputs
Outputs make selected values available outside the module. A caller can read a child module’s declared output through the module result; outputs can also appear in Terraform CLI output and HCP Terraform. HashiCorp’s Manage values in modules documentation explains module inputs, locals, and outputs.
output "normalized_service_name" {
description = "Normalized name used by the service module"
value = local.service_name_normalized
}
A parent can refer to this child output as module.service.normalized_service_name. If the module itself is repeated, the result’s shape follows the repetition method: a keyed map for for_each, or a list for count. HashiCorp recommends output descriptions and clear output names so the module’s interface is understandable.
Check examples against your project
Terraform language constructs are not tied to one provider, but resource arguments and nested block schemas are. Treat the examples above as illustrations of language behavior, not provider-ready resource definitions. Validate configuration against the Terraform version and provider versions pinned by your project, especially when relying on newer variable arguments such as ephemeral or const.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




