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

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)→4ceil(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)→4floor(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)→10max(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)→9pow(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:
1if number is positive-1if negative0if 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
| Function | Meaning | Example | Result |
abs(x) | Absolute value | abs(-10) | 10 |
ceil(x) | Round up | ceil(2.1) | 3 |
floor(x) | Round down | floor(4.9) | 4 |
max(a,b,c) | Largest value | max(2,7,5) | 7 |
min(a,b,c) | Smallest value | min(2,7,5) | 2 |
pow(a,b) | a^b | pow(2,3) | 8 |
log(base,num) | Logarithm | log(10,100) | 2 |
signum(x) | Sign of number | signum(-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_count | final_count |
| 0 | 1 |
| 1 | 1 |
| 3 | 3 |
| 5 | 5 |
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 reachinstance_countwith base 2ceil()→ rounds that uppow(2, …)→ calculates 2 raised to that rounded number
| instance_count | log2 | ceil | pow(2,ceil) | total_storage_gb |
| 1 | 0 | 0 | 1 | 1 |
| 2 | 1 | 1 | 2 | 2 |
| 3 | 1.58 | 2 | 4 | 4 |
| 5 | 2.32 | 3 | 8 | 8 |
🧠 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_count | instance_count / 2 | ceil | subnet_count |
| 2 | 1 | 1 | 1 |
| 3 | 1.5 | 2 | 2 |
| 4 | 2 | 2 | 2 |
| 5 | 2.5 | 3 | 3 |
💡 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:
| Function | What it does | Example | Output |
lower() | Converts all letters to lowercase | lower("Terraform") | "terraform" |
upper() | Converts all letters to uppercase | upper("Terraform") | "TERRAFORM" |
title() | Capitalizes the first letter of each word | title("hello world") | "Hello World" |
trim() | Removes specific characters from both ends | trim("..hello..", ".") | "hello" |
trimprefix() | Removes a specific word/prefix from the start | trimprefix("mr-bean", "mr-") | "bean" |
trimsuffix() | Removes a specific word/suffix from the end | trimsuffix("hello.txt", ".txt") | "hello" |
trimspace() | Removes spaces/tabs/newlines around text | trimspace(" 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 👇
| Function | Meaning | Example | Output |
substr(string, start, length) | Extract part of a string starting at position | substr("Terraform", 0, 4) | "Terr" |
replace(string, old, new) | Replace one word with another | replace("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 regex | regexall("[A-Z][a-z]+", "Hello World") | ["Hello", "World"] |
format(pattern, values...) | Format text using placeholders | format("%s-%03d", "web", 7) | "web-007" |
startswith(string, prefix) | Check if text begins with something | startswith("Terraform", "Ter") | true |
endswith(string, suffix) | Check if text ends with something | endswith("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:
| Function | Description | Example | Output |
split(separator, string) | Breaks a string into a list | split(",", "a,b,c") | ["a", "b", "c"] |
join(separator, list) | Joins list into one string | join("-", ["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 line | indent(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 Name | Description | Output |
resource_prefix | Creates a clean project + environment prefix | "myapp-dev" |
instance_names | Formats web server names dynamically | List of "myapp-dev-web-00X" |
domain | Extracts domain from email | "example.com" |
dns_name | Combines 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
| Function | Description | Example | Output |
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 nameeach.key— gives each instance name dynamicallytagsuse string functions to create readable tagsNumeric functions were already used in
locals.tfto 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
| Concept | Function Used | Purpose |
| Ensure minimum instance count | max() | Prevents 0 or negative numbers |
| Dynamic scaling formula | log(), ceil(), pow() | Calculates total storage size |
| Group instances per subnet | ceil() | Rounds up subnet numbers |
| Lowercase resource prefix | lower() | Consistent resource naming |
| Format instance names | format() | Generates web-001, web-002 style |
| Combine words into readable text | join() | 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!



