Skip to main content

Command Palette

Search for a command to run...

Day 14: Built-in Functions in Terraform Part-I

Published
15 min read
Day 14: Built-in Functions in Terraform Part-I
S

I'm a cloud-native enthusiast and tech blogger, sharing insights on Kubernetes, AWS, CI/CD, and Linux across my blog and Facebook page. Passionate about modern infrastructure and microservices, I aim to help others understand and leverage cloud-native technologies for scalable, efficient solutions.

Today, we’ll explore Terraform functions — powerful helpers that make your infrastructure more flexible and smart.

Terraform functions allow you to manipulate data, perform calculations, handle text, work with networks, and much more — all inside your configuration files.

🎯 Today’s Goals

By the end of this lesson, you’ll be able to:

  • ✅ Master essential Terraform functions

  • ✅ Understand string functions

  • ✅ Understand numeric functions

  • ✅ Apply them in real-world Terraform projects


🔢 Numeric Functions — Explained for Beginners

Terraform numeric functions help you perform mathematical calculations directly in your configuration files.
You can use them to calculate:

  • How many instances or subnets to create

  • How large a storage volume should be

  • How to round or constrain numeric inputs

Let’s go through each function carefully. 👇


🧮 Mathematical Operations

Here’s a breakdown of each numeric function from the example:


🧩 1. abs(number)Absolute Value

The absolute value means “ignore the negative sign.”

locals {
  absolute = abs(-15)
}

📘 Explanation:
If the number is negative, abs() turn it into a positive number.
If it’s already positive, it stays the same.

Example use case:
If you’re calculating a difference between two values (like used vs. available storage), you can use it abs() to make sure the result is always positive.

locals {
  storage_difference = abs(50 - 100)  # => 50
}

🧩 2. ceil(number)Round Up

Always rounds a decimal number up to the nearest whole number.

locals {
  ceiling = ceil(5.1)  # => 6
}

📘 Example:

  • ceil(3.1)4

  • ceil(7.9)8

💡 Use case:
You can use this when dividing resources and you want to avoid losing part of a unit.
For example, if each subnet can handle 2 servers, and you have 5 servers → ceil(5 / 2) = 3 subnets needed.


🧩 3. floor(number)Round Down

Always rounds a decimal number down to the nearest whole number.

locals {
  floored = floor(5.9)  # => 5
}

📘 Example:

  • floor(4.9)4

  • floor(6.3)6

💡 Use case:
If you need to calculate full “groups” of resources and you can’t have fractions (like 5.9 groups → only 5 real groups).


🧩 4. max(a, b, c, …)Find Maximum Value

Returns the largest number from a list of numbers.

locals {
  maximum = max(5, 12, 9)  # => 12
}

📘 Example:

  • max(3, 10, 7)10

  • max(0, -5, 8)8

💡 Use case:
You can use it max() to make sure something never goes below a minimum—for example, the number of servers should be at least 1.


🧩 5. min(a, b, c, …)Find Minimum Value

Returns the smallest number.

locals {
  minimum = min(5, 12, 9)  # => 5
}

💡 Use case:
When you want to limit a maximum value—for example, if a user requests 10 subnets but you only want to allow up to 6:

locals {
  subnet_count = min(var.requested_subnets, 6)
}

🧩 6. pow(base, exponent)Power Function

Raises a number to a power.
(Mathematically: base^exponent)

locals {
  power = pow(2, 3)  # => 8  (2×2×2)
}

📘 Example:

  • pow(3, 2)9

  • pow(10, 3)1000

💡 Use case:
Useful when you need exponential scaling—e.g., storage or CPU size that doubles with every increase in instance count.


🧩 7. log(base, number)Logarithm

Calculates how many times you multiply the base to get the number.

locals {
  logarithm = log(10, 100)  # => 2
}

📘 Explanation:
10² = 100 → That’s why log(10, 100) = 2.
It’s like saying, “How many times must I multiply 10 to get 100?”

💡 Use case:
You might use it log() when scaling values logarithmically—for instance, computing tiers or power-of-two sizes.


🧩 8. signum(number)Get the Sign

Returns:

  • 1 if number is positive

  • -1 if negative

  • 0 if zero

locals {
  sign = signum(-15)  # => -1
}

💡 Use case:
When you want to check the direction or “trend” of a value.
Example:

locals {
  delta = var.new_value - var.old_value
  direction = signum(local.delta)
}
# If new_value > old_value → +1 (increase)
# If new_value < old_value → -1 (decrease)
# If same → 0

🧠 Summary Table

FunctionMeaningExampleResult
abs(x)Absolute valueabs(-10)10
ceil(x)Round upceil(2.1)3
floor(x)Round downfloor(4.9)4
max(a,b,c)Largest valuemax(2,7,5)7
min(a,b,c)Smallest valuemin(2,7,5)2
pow(a,b)a^bpow(2,3)8
log(base,num)Logarithmlog(10,100)2
signum(x)Sign of numbersignum(-15)-1

🧰 Practical Terraform Example

Now, let’s look at the real Terraform snippet and explain each part 👇

variable "instance_count" {
  type = number
}

locals {
  # Ensure minimum of 1 instance
  final_count = max(1, var.instance_count)

  # Calculate storage based on instance count
  total_storage_gb = pow(2, ceil(log(var.instance_count, 2)))

  # Round up subnet count
  subnet_count = ceil(var.instance_count / 2.0)
}

💡 Step-by-step Explanation

1️⃣ final_count = max(1, var.instance_count)

Ensures that even if someone sets instance_count = 0, Terraform will still create at least 1 instance.

instance_countfinal_count
01
11
33
55

2️⃣ total_storage_gb = pow(2, ceil(log(var.instance_count, 2)))

This line automatically scales storage capacity using a power of two rule.

Let’s break it down:

  • log(var.instance_count, 2) → finds the power needed to reach instance_count with base 2

  • ceil() → rounds that up

  • pow(2, …) → calculates 2 raised to that rounded number

instance_countlog2ceilpow(2,ceil)total_storage_gb
10011
21122
31.58244
52.32388

🧠 Meaning:
If you scale up instances, your total storage scales automatically in powers of two — a common pattern in cloud infrastructure design.


3️⃣ subnet_count = ceil(var.instance_count / 2.0)

This means each subnet will hold 2 instances, and Terraform will round up if there’s an extra one.

instance_countinstance_count / 2ceilsubnet_count
2111
31.522
4222
52.533

💡 This ensures no instance is left without a subnet.


🧪 Try It Yourself in Terraform Console

You can test these functions directly in Terraform:

terraform console
> abs(-20)
> ceil(4.3)
> floor(9.8)
> max(1, 9, 5)
> pow(2, 4)
> log(10, 1000)
> signum(-99)
> exit

🔤 Terraform String Functions

Terraform string functions help you manipulate, search, and format text.

You’ll use these functions a lot to:

  • Build clean resource names

  • Format instance IDs, tags, or DNS names

  • Extract or modify text values dynamically


🎯 What is a String?

A string is simply text inside quotes, e.g. "Terraform", "hello world" ", " "web-server-001"


🧰 1. String Manipulation Functions

These functions change the appearance of a string — like converting to lowercase, trimming spaces, or removing parts of it.

locals {
  name = "Terraform"

  lowercase   = lower(name)
  uppercase   = upper(name)
  titlecase   = title("hello world")
  trimmed     = trim("  hello  ", " ")
  trimprefix  = trimprefix("mr-bean", "mr-")
  trimsuffix  = trimsuffix("hello.txt", ".txt")
  cleaned     = trimspace("  hello world  ")
}

🔍 Let’s explain each one:

FunctionWhat it doesExampleOutput
lower()Converts all letters to lowercaselower("Terraform")"terraform"
upper()Converts all letters to uppercaseupper("Terraform")"TERRAFORM"
title()Capitalizes the first letter of each wordtitle("hello world")"Hello World"
trim()Removes specific characters from both endstrim("..hello..", ".")"hello"
trimprefix()Removes a specific word/prefix from the starttrimprefix("mr-bean", "mr-")"bean"
trimsuffix()Removes a specific word/suffix from the endtrimsuffix("hello.txt", ".txt")"hello"
trimspace()Removes spaces/tabs/newlines around texttrimspace(" hi ")"hi"

💡 Real-world Example

Imagine your project name input is messy:

variable "project_name" {
  default = "  MyAPP  "
}

You can clean and format it for consistent naming:

locals {
  clean_name = lower(trimspace(var.project_name))
}
# "myapp"

Now your resource names will be uniform and lowercase.


🔍 2. String Searching and Replacement

These functions help you extract, replace, and format parts of text.

locals {
  text = "Hello, World!"

  substring = substr(text, 0, 5)
  replaced  = replace(text, "World", "Terraform")
  extracted = regex("([A-Z][a-z]+)", text)
  all_words = regexall("[A-Z][a-z]+", text)
  formatted = format("The answer is %d", 42)
  formatted2 = format("%s-%s-%03d", "web", "server", 5)
  starts     = startswith(text, "Hello")
  ends       = endswith(text, "!")
}

Let’s look at each in detail 👇

FunctionMeaningExampleOutput
substr(string, start, length)Extract part of a string starting at positionsubstr("Terraform", 0, 4)"Terr"
replace(string, old, new)Replace one word with anotherreplace("hello world", "world", "Terraform")"hello Terraform"
regex(pattern, text)Extract first match using regex (pattern search)regex("[A-Z][a-z]+", "Hello World")"Hello"
regexall(pattern, text)Find all matches using regexregexall("[A-Z][a-z]+", "Hello World")["Hello", "World"]
format(pattern, values...)Format text using placeholdersformat("%s-%03d", "web", 7)"web-007"
startswith(string, prefix)Check if text begins with somethingstartswith("Terraform", "Ter")true
endswith(string, suffix)Check if text ends with somethingendswith("Terraform", "form")true

🧩 Example Breakdown:

locals {
  text = "Hello, World!"
  substring = substr(text, 0, 5)  # "Hello"
  replaced  = replace(text, "World", "Terraform")  # "Hello, Terraform!"
}

🧠 Explanation:

  • substr(text, 0, 5) → starts from position 0 (the first letter), takes 5 letters.

  • replace() → looks for "World" and swaps it with "Terraform".


💡 Real-world Use Case

You can use string formatting for naming multiple instances:

locals {
  environment = "dev"
  server_name = format("%s-%s-%03d", "myapp", local.environment, 1)
}
# Output: "myapp-dev-001"

This makes your resource names look clean and consistent like:

myapp-dev-001  
myapp-dev-002  
myapp-dev-003

🧩 3. String Splitting and Joining

You often need to break apart or combine strings in Terraform — for example, splitting an email or joining tags into a single line.

locals {
  words    = split(",", "apple,banana,orange")
  csv      = join(",", ["apple", "banana", "orange"])
  chomped  = chomp("hello\n")
  indented = indent(2, "hello\nworld")
}

Explanation:

FunctionDescriptionExampleOutput
split(separator, string)Breaks a string into a listsplit(",", "a,b,c")["a", "b", "c"]
join(separator, list)Joins list into one stringjoin("-", ["a", "b", "c"])"a-b-c"
chomp(string)Removes a trailing newline (\n)chomp("hello\n")"hello"
indent(spaces, text)Adds spaces to the beginning of each lineindent(2, "hi\nworld")" hi\n world"

💡 Real-world Example: Extracting Email Domain

locals {
  email  = "admin@example.com"
  domain = split("@", local.email)[1]
}
# Output: "admin", "example.com"

🧠 Explanation:
split("@", local.email) → splits into ["admin", "example.com"]
[1] → gets the second element (index starts at 0)


🧱 4. Practical String Example in Terraform

Let’s put all the string functions together into a real Terraform example 👇

variable "environment" {
  default = "dev"
}

variable "project_name" {
  default = "MyApp"
}

locals {
  # 1️⃣ Make clean prefix (lowercase, environment)
  resource_prefix = "${lower(var.project_name)}-${var.environment}"
  # => "myapp-dev"

  # 2️⃣ Format instance names (like server IDs)
  instance_names = [
    for i in range(3) :
    format("%s-web-%03d", local.resource_prefix, i + 1)
  ]
  # => ["myapp-dev-web-001", "myapp-dev-web-002", "myapp-dev-web-003"]

  # 3️⃣ Extract domain name from admin email
  admin_email = "admin@example.com"
  domain      = split("@", local.admin_email)[1]
  # => "example.com"

  # 4️⃣ Build DNS name dynamically
  dns_name = "${local.resource_prefix}.${local.domain}"
  # => "myapp-dev.example.com"
}

🧠 Explanation:

Local NameDescriptionOutput
resource_prefixCreates a clean project + environment prefix"myapp-dev"
instance_namesFormats web server names dynamicallyList of "myapp-dev-web-00X"
domainExtracts domain from email"example.com"
dns_nameCombines prefix + domain"myapp-dev.example.com"

🧪 Try it in Terraform Console

You can test any string function easily:

terraform console
> lower("Terraform")
> upper("cloud")
> split(",", "a,b,c")
> join("-", ["apple", "banana"])
> format("%s-%03d", "web", 7)
> replace("dev", "d", "prod")
> exit

🧭 Summary Table

FunctionDescriptionExampleOutput
lower()Lowercase all letters"HELLO""hello"
upper()Uppercase all letters"hello""HELLO"
title()Capitalize words"hi world""Hi World"
trimspace()Remove spaces around text" hi ""hi"
replace()Replace text"hi world""hi Terraform"
split()Split into list"a,b,c"["a","b","c"]
join()Join list into string["a","b","c"]"a-b-c"
format()Build strings with patterns"web-%03d" + 5 → "web-005"
startswith()Checks beginning of text"Terraform"true
endswith()Checks end of text"Terraform"true

🧪 Hands-On Lab

📁 Project Structure

terraform-numeric-string-lab/
├── main.tf
├── variables.tf
├── outputs.tf
└── locals.tf

🧰 Step 1: variables.tf

This file defines user inputs.
We’ll use variables for project naming, instance count, and region.

# variables.tf

variable "aws_region" {
  description = "AWS region where EC2 instances will be created"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  description = "Name of your project"
  type        = string
  default     = "NumericStringDemo"
}

variable "environment" {
  description = "Deployment environment (dev, stage, prod)"
  type        = string
  default     = "dev"
}

variable "instance_count" {
  description = "Number of instances you want to create"
  type        = number
  default     = 3
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "base_storage" {
  description = "Base storage size (GB) for each instance"
  type        = number
  default     = 10
}

🧮 Step 2: locals.tf

Here’s where we use numeric and string functions to calculate and generate smart values.

# locals.tf

locals {
  # ✅ String Functions

  # Create a consistent lowercase prefix for all resources
  resource_prefix = "${lower(var.project_name)}-${lower(var.environment)}"
  # Example: "numericstringdemo-dev"

  # Format EC2 instance names dynamically
  instance_names = [
    for i in range(var.instance_count) :
    format("%s-web-%03d", local.resource_prefix, i + 1)
  ]
  # ["numericstringdemo-dev-web-001", "numericstringdemo-dev-web-002", ...]

  # ✅ Numeric Functions

  # Ensure we have at least 1 instance
  final_instance_count = max(1, var.instance_count)

  # Calculate total storage: power of 2 * base storage
  # e.g., if count=3 => ceil(log(3,2))=2 => pow(2,2)=4 => 4 * 10 = 40GB total
  total_storage_gb = pow(2, ceil(log(local.final_instance_count, 2))) * var.base_storage

  # Divide instances into subnets (2 per subnet, rounded up)
  subnet_count = ceil(local.final_instance_count / 2.0)

  # String join example: create a simple description
  description = join(" ", [
    "Project:", var.project_name,
    "Environment:", var.environment,
    "Instances:", tostring(local.final_instance_count)
  ])
}

☁️ Step 3: main.tf

Now, we’ll use those locals to dynamically create EC2 instances with smart naming and scaling logic.

# main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# ✅ Data source to fetch latest Amazon Linux AMI
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# ✅ Create EC2 instances using for_each
resource "aws_instance" "web" {
  for_each = toset(local.instance_names)

  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type

  tags = {
    Name        = each.key
    Environment = var.environment
    Description = local.description
  }
}

🧠 Explanation:

  • for_each = toset(local.instance_names) — creates one instance per formatted name

  • each.key — gives each instance name dynamically

  • tags use string functions to create readable tags

  • Numeric functions were already used in locals.tf to decide count, storage, and subnet count


📤 Step 4: outputs.tf

We’ll show computed results and demonstrate how our functions affected the setup.

# outputs.tf

output "instance_names" {
  description = "List of generated EC2 instance names"
  value       = local.instance_names
}

output "total_storage_gb" {
  description = "Total calculated storage based on instance count"
  value       = local.total_storage_gb
}

output "subnet_count" {
  description = "Calculated subnet count (2 instances per subnet)"
  value       = local.subnet_count
}

output "description" {
  description = "Auto-generated description using join and tostring"
  value       = local.description
}

🧩 Step 5: Run and Test

✅ Initialize Terraform

terraform init

✅ Validate your code

terraform validate

✅ Preview your plan

terraform plan

You’ll see something like

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + instance_names = [
      "numericstringdemo-dev-web-001",
      "numericstringdemo-dev-web-002",
      "numericstringdemo-dev-web-003",
    ]
  + total_storage_gb = 40
  + subnet_count = 2
  + description = "Project: NumericStringDemo Environment: dev Instances: 3"

✅ Deploy your lab

terraform apply

✅ Check outputs

terraform output

Expected output:

instance_names = [
  "numericstringdemo-dev-web-001",
  "numericstringdemo-dev-web-002",
  "numericstringdemo-dev-web-003",
]
total_storage_gb = 40
subnet_count = 2
description = "Project: NumericStringDemo Environment: dev Instances: 3"

✅ Clean up

terraform destroy -auto-approve

🧠 What You Learned

ConceptFunction UsedPurpose
Ensure minimum instance countmax()Prevents 0 or negative numbers
Dynamic scaling formulalog(), ceil(), pow()Calculates total storage size
Group instances per subnetceil()Rounds up subnet numbers
Lowercase resource prefixlower()Consistent resource naming
Format instance namesformat()Generates web-001, web-002 style
Combine words into readable textjoin()Builds description text

🚀 Tomorrow’s Preview

Day 16: Dynamic Blocks for Flexible Resources

Tomorrow we’ll:

  • Collection functions (merge, lookup, flatten)

  • Encoding functions (base64, json, yaml)

  • Crypto functions (md5, sha256, bcrypt)

  • Date/time functions (timestamp, formatdate)

  • Network functions (cidrsubnet, cidrhost)

  • Filesystem functions (file, templatefile)


← Day 13: Conditionals & Logic | Day 15: Dynamic Blocks →


Remember: Functions are powerful tools for data transformation and manipulation!

T

Thank you!

More from this blog

S

StackOps - Diary

33 posts

Welcome to the StackOps - Diary. We’re dedicated to empowering the tech community. We delve into cloud-native and microservices technologies, sharing knowledge to build modern, scalable solutions.