Terraform Strings Part 1: The Foundation - Defining and Interpolating

Terraform Strings Part 1 covers quoting, heredocs, interpolation and key functions—everything you need to start building dynamic HCL configs.

Welcome to our three-part series on mastering strings in Terraform! Strings are the backbone of your Infrastructure as Code (IaC) configurations, letting you define everything from resource names to dynamic script content. Getting comfortable with how Terraform handles strings is a crucial step in becoming a proficient Terraform user.

In this first post, we'll lay the groundwork by exploring how to define strings using quoted literals and heredocs. Then, we'll unlock the power of dynamic configurations by learning about string interpolation.

What's a String in Terraform?

In Terraform, a string is a sequence of characters used to represent text. You'll use strings constantly:

  • Naming your resources (like EC2 instances or S3 buckets).
  • Setting tags for organization and billing.
  • Writing user data scripts for server initialization.
  • Defining output values to display information after your infrastructure is built.

Terraform's HashiCorp Configuration Language (HCL) provides flexible ways to define and manipulate these strings.

Defining Your Strings: Literals and Heredocs

Terraform offers two main ways to write strings: quoted string literals and heredoc syntax.

1. Quoted String Literals ("")

This is the most common and straightforward way to define a string, especially for single-line text. You simply enclose your text in double quotes (").

variable "instance_name_prefix" {
  type    = string
  default = "my-app-server"
  description = "The prefix for server names."
}

output "greeting_message" {
  value = "Hello, Terraform World!"
}

Escape Sequences: What if your string needs to include a double quote, or a newline character? That's where escape sequences come in. These are special character combinations starting with a backslash (\):

  • \n: Newline
  • \r: Carriage Return
  • \t: Tab
  • \": Literal double quote
  • \\: Literal backslash

For example:

locals {
  complex_message = "He said, \"Terraform is awesome!\"\nThis is on a new line."
}

The local.complex_message would render as:

He said, "Terraform is awesome!"
This is on a new line.

2. Heredoc String Literals (<<EOF and <<-EOF)

When you need to write multi-line strings, like a shell script or a JSON policy document, quoted strings with lots of \n characters can become messy. Heredocs to the rescue! Inspired by Unix shells, heredocs allow you to define blocks of text more naturally.

General Syntax: A heredoc starts with <<DELIMITER or <<-DELIMITER, followed by your chosen delimiter (a keyword like EOF or EOT), and then a newline. The string content follows, and it ends with the same delimiter on its own line.

resource "local_file" "example_script" {
  filename = "${path.module}/myscript.sh"
  content  = <<EOF
#!/bin/bash
echo "Hello from my script!"
date
EOF
}

There are two types of heredocs, differing in how they handle indentation:

Indented Heredocs (<<-DELIMITER): This is usually what you want for cleaner HCL! It strips common leading whitespace from your string content. Terraform looks for the least indented line (or the indentation of the closing delimiter) and removes that amount of space from all lines.

resource "aws_instance" "web_server" {
  # ... other configurations ...
  user_data = <<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl start nginx
    echo "Nginx installed and started!" > /tmp/nginx_status.txt
  EOF
}

In this user_data example, even though the script lines are indented in our Terraform code to look neat, the <<-EOF ensures that the actual script content passed to the instance won't have those leading HCL-related spaces. This is a huge win for readability!

Standard Heredocs (<<DELIMITER): These preserve all leading whitespace (indentation) on each line of your string.

resource "local_file" "standard_heredoc" {
  filename = "${path.module}/standard.txt"
  content  = <<EOF
    This line is indented in the HCL.
      And this line will have its indentation preserved in the file.
EOF
}

If your HCL code is indented for readability, that indentation will become part of the string. This can be problematic if the embedded content (like YAML) is sensitive to leading spaces.

Choosing Delimiters: While EOF (End Of File) is a common convention, you can use any valid identifier. This is handy if your string content itself needs to contain the word "EOF".

Making Strings Dynamic: Interpolation (${...})

Static strings are useful, but the real power comes when you make them dynamic. String interpolation in Terraform allows you to embed expressions directly within your strings. These expressions are evaluated, and their results are inserted into the string.

The Syntax: ${expression}

Terraform evaluates whatever is inside the ${...}. If the result isn't already a string (like a number or boolean), Terraform automatically converts it.

What Can You Interpolate?

each.key / each.value (for for_each loops):

variable "user_vms" {
  type = map(string)
  default = {
    "alice" = "t2.small"
    "bob"   = "t2.medium"
  }
}

resource "aws_instance" "user_specific_vms" {
  for_each      = var.user_vms
  ami           = "ami-0c55b31ad2c359a57" # Example AMI
  instance_type = each.value             # The instance type from the map
  tags = {
    Name  = "vm-for-${each.key}"         # vm-for-alice, vm-for-bob
    Owner = each.key
  }
}

count.index (for resource loops):

resource "aws_instance" "web_nodes" {
  count         = 2
  ami           = "ami-0c55b31ad2c359a57" # Example AMI
  instance_type = "t2.micro"
  tags = {
    Name = "web-node-${count.index + 1}" # Creates web-node-1, web-node-2
  }
}

Function Calls: Use Terraform's built-in functions.

output "module_path_info" {
  value = "This module is located at: ${path.module}"
}

Elements from Lists/Maps:

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

variable "server_config" {
  type = map(string)
  default = {
    "cpu" = "2"
    "ram" = "4GB"
  }
}

output "first_az" {
  value = "Primary AZ: ${var.availability_zones[0]}"
}

output "server_ram" {
  value = "Server RAM: ${var.server_config["ram"]}"
}

Resource Attributes: Access attributes of resources you've defined.

resource "aws_instance" "example" {
  ami           = "ami-0c55b31ad2c359a57" # Example AMI
  instance_type = "t2.micro"
}

output "instance_id_info" {
  value = "The new instance ID is: ${aws_instance.example.id}"
}

Input Variables:

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

output "selected_region_message" {
  value = "We are deploying to the ${var.aws_region} region."
}

Simple Math in Interpolations

You can even do basic arithmetic:

variable "base_app_port" {
  type    = number
  default = 8080
}

resource "null_resource" "port_calculator" {
  count = 3
  provisioner "local-exec" {
    # This is just for demonstration; direct interpolation is preferred for simple cases
    command = "echo 'App ${count.index + 1} will run on port ${var.base_app_port + count.index}'"
  }
}

output "app_ports_info" {
  value = "App 1 port: ${var.base_app_port + 0}, App 2 port: ${var.base_app_port + 1}"
}

Escaping Interpolation: The Literal ${

What if you need the literal characters ${ in your string, for example, when generating a script that uses environment variables? Use a double dollar sign $$ to escape it:

resource "local_file" "shell_script_example" {
  filename = "${path.module}/env_script.sh"
  content  = <<-EOF
    #!/bin/bash
    # This will be interpolated by Terraform:
    echo "Terraform variable 'aws_region': ${var.aws_region}"

    # This will be a literal shell variable expansion:
    echo "Current shell user: $${USER}"
    echo "Home directory: $${HOME}/data"
  EOF
}

In env_script.sh, $${USER} will become ${USER}, which the shell will then interpret.

Stay Tuned!

You've now got a solid grasp of the basics: defining strings with quotes and heredocs, and making them dynamic with interpolation. These are fundamental building blocks for any Terraform project.

In Part 2 of this series, we'll explore more advanced territory:

  • Using template directives like %{if} and %{for} for conditional logic and loops within your strings.
  • Diving into Terraform's rich library of built-in string functions to transform and manipulate string data.

Keep practicing, and happy Terraforming!