Terraform’s expression and function system is the core “thinking engine” behind your configurations: expressions describe what value an argument should have, and functions are reusable tools you invoke inside those expressions to compute values dynamically.
1. What Is an Expression in Terraform?
An expression is any piece of HCL that Terraform can evaluate to a concrete value: a string, number, bool, list, map, or object. You use expressions in almost every place on the right-hand side of arguments, in locals, count, for_each, dynamic blocks, and more.
Common expression forms:
- Literals:
"hello",5,true,null,["a", "b"],{ env = "dev" }. - References:
var.region,local.tags,aws_instance.app.id,module.vpc.vpc_id. - Operators: arithmetic (
+ - * / %), comparisons (< > <= >=), equality (== !=), logical (&& || !). - Conditionals:
condition ? value_if_true : value_if_false. forexpressions:[for s in var.subnets : upper(s)]to transform collections.- Splat expressions:
aws_instance.app[*].idto project attributes out of collections.
Rationale: Terraform must stay declarative (you describe the desired state), but real infrastructure is not static; expressions give you a minimal “language” to derive values from other values without dropping into a general-purpose programming language.
2. What Is a Function?
A function is a built‑in helper you call inside expressions to transform or combine values. The syntax is a function name followed by comma‑separated arguments in parentheses, for example max(5, 12, 9). Functions always return a value, so they can appear anywhere a normal expression is allowed.
Key properties:
- Terraform ships many built‑in functions (string, numeric, collection, IP/network, crypto, time, type conversion, etc.).
- You cannot define your own functions in HCL; you only use built‑ins, plus any provider-defined functions a provider may export.
- Provider-defined functions are namespaced like
provider::<local-name>::function_name(...)when used.
Examples of useful built‑in functions:
- String:
upper("dev"),lower(),format(),join("-", ["app", "dev"]). - Numeric:
max(5, 12, 9),min(),ceil(),floor(). - Collection:
length(var.subnets),merge(local.tags, local.extra_tags),flatten().
Rationale: Functions cover the common transformation needs (naming, list/map manipulation, math) so that your Terraform remains expressive but compact, and you avoid copy‑pasting “string‑mangling” logic everywhere.
3. How Expressions and Functions Work Together
Terraform’s model is expression‑centric: on the right‑hand side of almost every argument, you write an expression, and function calls are just one kind of expression. You freely compose references, operators, conditionals, for expressions, and functions, as long as the input and output types match.
Typical composition patterns:
- Use references (
var.*,local.*, resource attributes) as the base inputs. - Apply operators and conditional expressions to make decisions (
var.env == "prod" ? 3 : 1). - Use
forexpressions and collection functions to reshape data ([for s in var.subnets : upper(s)]). - Use string functions to build consistent resource names (
format("%s-%s", var.app, var.env)).
From a mental-model perspective, a good way to think about this is: “Everything dynamic lives in expressions; functions are building blocks inside those expressions.”
4. A Small Example
Below is a minimal Terraform configuration that showcases expressions and functions together, and that you can actually run to observe evaluation results.
Example configuration (main.tf)
terraform {
required_version = ">= 1.6"
}
variable "environment" {
type = string
default = "dev"
}
variable "app_servers" {
type = list(string)
default = ["app-1", "app-2", "app-3"]
}
locals {
# Expression: equality operator -> bool
is_prod = var.environment == "prod"
# Literal map and reference
base_tags = {
app = "payments"
environment = var.environment
}
# For expression + string function
uppercased_servers = [for s in var.app_servers : upper(s)]
# Merge and format functions to compute a name once
common_tags = merge(
local.base_tags,
{
name = format(
"%s-%s-%02d",
local.base_tags.app,
local.base_tags.environment,
length(var.app_servers)
)
}
)
}
output "summary" {
value = {
is_prod = local.is_prod
uppercased_servers = local.uppercased_servers
common_tags = local.common_tags
}
}
What this demonstrates conceptually:
- Expressions:
var.environment == "prod"produces a bool forlocal.is_prod.- The map in
local.base_tagsuses both literals and references. - The
localsblock itself is a way to give names to intermediate expressions.
- Functions:
upper(s)transforms each server name to uppercase inside aforexpression.length(var.app_servers)computes the number of servers.format("%s-%s-%02d", ...)builds a stable name string.merge(...)combines two maps into a single tag map.
Rationale: This pattern—variables + locals + expressions + functions—is exactly how you avoid repetition and keep a production Terraform codebase readable as it grows.
5. How to Validate This Example
Terraform provides an expression console and standard workflow commands to validate that your expressions and functions behave as expected before they affect real infrastructure.
Option A: Run the configuration
- Save the example as
main.tfin an empty directory. - Run:
terraform initto set up the working directory.terraform apply -auto-approveto evaluate and show outputs.
- Observe the
summaryoutput:is_prodshould befalse(with the default environmentdev).uppercased_serversshould be["APP-1", "APP-2", "APP-3"].common_tags.nameshould bepayments-dev-03.
To see how expressions react to different inputs, run again with a different environment:
terraform apply -auto-approve -var 'environment=prod'
Now is_prod will be true, and the computed name will switch to payments-prod-03, even though you haven’t changed any resource definitions.
Option B: Experiment interactively with terraform console
Terraform’s console lets you test expressions and functions on the fly.
From the same directory:
terraform console
Then try:
> 1 + 2 * 3
> var.environment == "prod"
> [for s in var.app_servers : upper(s)]
> merge({a = 1}, {b = 2})
> format("%s-%s", "app", var.environment)
You will see the evaluated results immediately, which is ideal for teaching yourself how a particular expression or function behaves before embedding it into a real module.
6. Summary Table: Expressions vs Functions
| Aspect | Expressions | Functions |
|---|---|---|
| Purpose | Describe how to compute a value. | Provide reusable operations used inside expressions. |
| Examples | var.env == "prod", [for s in xs : x.id]. |
length(var.subnets), join("-", local.tags). |
| Defined by | Terraform language syntax. | Terraform’s built‑in and provider-defined function library. |
| Customization | Composed via locals, variables, and blocks. | No user-defined functions in HCL; only built‑ins/providers. |
| Typical usage domain | Conditionals, loops, references, constructing structures. | String formatting, math, collection manipulation, conversion. |
Leave a Reply