cd ..
TERRAFORM

Expressions in Terraform: Values That Make HCL Dynamic

In the first Terraform examples, it’s common to fill arguments directly: an AMI, an instance type, and some tags. But a real configuration needs to reuse values, query attributes from other resources, and change behavior according to the environment. In HCL, we do this with expressions.

What is an expression

An expression represents or calculates a value. The simplest example is a literal value:

instance_type = "t3.micro"

The string "t3.micro" is already an expression. The same applies to numbers, booleans, lists, and maps:

enabled = true
ports   = [80, 443]

tags = {
  Name        = "web"
  Environment = "dev"
}

The power of expressions appears when the value is no longer fixed and becomes derived from another part of the configuration.

Referencing values

An expression can read variables, local values, data sources, resources, and modules:

variable "instance_type" {
  type    = string
  default = "t3.micro"
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  subnet_id     = aws_subnet.public.id
}

output "public_ip" {
  value = aws_instance.web.public_ip
}

Here, three different references appear:

Upon encountering the last reference, Terraform also understands that the instance depends on the subnet and automatically organizes the order of operations.

Local values and functions

Local values help name an expression and avoid repetition:

variable "environment" {
  type = string
}

locals {
  name_prefix = "commandlinux-${var.environment}"

  common_tags = {
    Project     = "commandlinux"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

Afterward, the values can be used in multiple resources:

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.environment == "production" ? "t3.small" : "t3.micro"
  tags          = merge(local.common_tags, { Name = "${local.name_prefix}-web" })
}

This snippet combines several language features:

When the entire string is just a reference, interpolation is not necessary. Prefer ami = data.aws_ami.ubuntu.id over ami = "${data.aws_ami.ubuntu.id}".

Collections and for

Expressions also transform lists and maps. For example, to normalize a list of names:

variable "services" {
  type    = list(string)
  default = ["API", "WEB", "WORKER"]
}

locals {
  service_names = [for service in var.services : lower(service)]
}

The result of local.service_names will be:

["api", "web", "worker"]

It’s also possible to filter items:

locals {
  long_names = [for service in var.services : lower(service) if length(service) > 3]
}

Not every place accepts every expression

Expressions appear almost everywhere in the language, but some contexts require values known from the start. Backend configuration is a good example: it’s processed during terraform init, before variables, resources, and data sources are available.

Therefore, this does not work:

terraform {
  backend "s3" {
    bucket = var.state_bucket
  }
}

In this case, variable values can be provided by partial backend configuration, via a dedicated file, or using the -backend-config option, being careful not to store credentials in these locations.

Testing with terraform console

The command below opens an interactive console to experiment with expressions in the context of the current configuration:

terraform console

Some possible tests:

> upper("terraform")
"TERRAFORM"

> 2 * 3
6

> [for value in [1, 2, 3] : value * 2]
[
  2,
  4,
  6,
]

This console is a quick way to validate a transformation before placing it in a resource.

Conclusion

Expressions are what transform a static HCL file into a reusable configuration. Literal values are still expressions, but references, conditionals, functions, and for expressions allow connecting resources and adapting infrastructure without duplicating blocks.

The practical rule is to start simple and extract an expression when it avoids repetition or makes a relationship clearer. If the calculation becomes difficult to read, it probably deserves a name in locals.

References

What did you think?