Skip to main content

Terraform Ep 6: Local Values, Built-in Functions, and Interpolation

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
terraform - This article is part of a series.
Part 6: This Article
Welcome to Tier 2: Intermediate Constructs. Until now, we have written mostly static HCL code. Now, we will inject programmatic logic into our configurations. We will manipulate strings, perform mathematical calculations, and centralize complex logic using Locals and Built-in Functions.

1. String Interpolation and Directives
#

In most modern programming languages, you can dynamically insert variables into strings (e.g., Template Literals in JavaScript `Hello ${name}`). Terraform supports this natively via Interpolation.

Basic Interpolation
#

To inject a variable or an attribute from another resource into a string, wrap it in ${...}.

variable "environment" {
  type    = string
  default = "production"
}

resource "aws_s3_bucket" "app_data" {
  # The resulting bucket name will be: "company-app-data-production"
  bucket = "company-app-data-${var.environment}"
}

Conditional Directives
#

Terraform allows you to perform basic inline logic, such as an if/else statement using the ternary operator condition ? true_val : false_val.

resource "aws_instance" "web" {
  ami = "ami-12345"
  
  # If the environment is production, use a large instance. Otherwise, use a micro.
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
}

2. The Power of locals (Keeping Code DRY)
#

Imagine you have 15 different resources in your main.tf file, and you want to tag all of them with a standard naming convention: ProjectName-Environment-Region.

If you use standard string interpolation on every single resource block, your code violates the DRY (Don’t Repeat Yourself) principle. If the naming convention changes tomorrow, you must update it in 15 different places.

This is where the locals block shines. A local value assigns a name to an expression, allowing you to use it multiple times within a module without repeating it.

Practice: Centralizing Logic with Locals
#

Create a new directory named terraform-locals:

mkdir terraform-locals
cd terraform-locals

Create a main.tf file and paste the following:

variable "project" {
  default = "payment-gateway"
}

variable "environment" {
  default = "staging"
}

# 1. Define the Locals
locals {
  # Centralized naming convention logic
  common_prefix = "${var.project}-${var.environment}"
  
  # Centralized tags to be applied to all resources
  standard_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

# 2. Use the Locals
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  
  tags = merge(
    local.standard_tags,
    { Name = "${local.common_prefix}-vpc" }
  )
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
  
  tags = merge(
    local.standard_tags,
    { Name = "${local.common_prefix}-public-subnet" }
  )
}

Notice how we access the values using the local. prefix (not locals.). By centralizing the common_prefix and standard_tags, any future changes to the tagging strategy only need to be made in one single place.


3. Terraform Built-in Functions
#

Terraform comes with dozens of built-in functions to transform and combine values. Note that Terraform does not support user-defined functions; you must use the ones provided by the HCL language.

Here are the most critical functions every Platform Engineer must know:

String Functions
#

FunctionDescriptionExampleResult
lower()Converts all characters to lowercaselower("PROD")"prod"
upper()Converts all characters to uppercaseupper("dev")"DEV"
replace()Replaces substringsreplace("hello-world", "-", "_")"hello_world"
join()Combines a list into a stringjoin(",", ["a", "b", "c"])"a,b,c"

Collection Functions
#

FunctionDescriptionExampleResult
length()Returns the number of items in a list or stringlength(["a", "b", "c"])3
concat()Combines two or more listsconcat(["a"], ["b"])["a", "b"]
merge()Combines two or more maps (dictionaries)merge({a=1}, {b=2}){a=1, b=2}
keys()Returns a list of keys from a mapkeys({a=1, b=2})["a", "b"]

File and Encoding Functions
#

Often, you need to pass a bash script into an EC2 instance’s user_data field to execute upon boot. Instead of hardcoding a massive string in your HCL, you can use the file() function.

resource "aws_instance" "web" {
  ami           = "ami-12345"
  instance_type = "t3.micro"
  
  # Reads the bash script from disk and injects it as a string
  user_data = file("${path.module}/setup.sh")
}

Practice: Using the Terraform Console
#

You do not need to run a full terraform apply to test functions. Terraform provides an interactive REPL (Read-Eval-Print Loop) console, similar to Python’s or Node.js’s terminal console.

Run the following command in your terminal:

terraform console

Now, try typing some function expressions directly into the prompt:

> max(5, 12, 9)
12
> split(",", "foo,bar,baz")
[
  "foo",
  "bar",
  "baz",
]
> cidrsubnet("10.0.0.0/16", 8, 2)
"10.0.2.0/24"

Type exit to leave the console. The terraform console is your best friend when debugging complex locals logic.


Troubleshooting & Common Errors
#

  1. Call to function "file" failed: no file exists at...

    • Root Cause: The file() function requires the file to exist on the disk before Terraform runs. You cannot use file() to read a file that Terraform itself is generating in the same run.
    • Solution: Verify the path. Always use the interpolation ${path.module}/filename to ensure relative paths resolve correctly regardless of where the CLI is executed from.
  2. Error: local value cannot reference itself

    • Root Cause: You accidentally created a circular reference inside your locals block (e.g., a = local.b and b = local.a).
    • Solution: Break the cycle. Locals evaluate purely based on their definitions; they cannot be recursive.

Conclusion & Next Steps
#

You have now injected dynamic intelligence into your infrastructure code. By utilizing locals, you have made your code DRY, and by leveraging built-in functions, you can manipulate data structures effortlessly.

However, what if you need to create 5 identical EC2 instances? Copy-pasting the resource block 5 times is unacceptable.

In Episode 7: Advanced Looping Strategies, we will master the art of infrastructure iteration using the count and for_each meta-arguments.

terraform - This article is part of a series.
Part 6: This Article