Infra chaos crushing your controls?

Meet Spacelift at AWS re:Invent

How to Use Tuples in Terraform [Example]

terraform

Terraform tuples enable more structured, predictable configurations by grouping related values together in a fixed order. In this article, we’ll walk through some examples, showing how to use tuples in local variables, module inputs, and practical infrastructure setups.

What is a Terraform tuple?

A Terraform tuple is an ordered collection of values that can be of different types. Tuples are defined using square brackets [] and maintain the sequence of elements. 

In Terraform’s type system, a tuple is a fixed-length list where the position of each element determines its expected type. 

Unlike lists, which require all elements to be of the same type, tuples allow mixed types and validate each element by its position. Lists also use [], so the difference is in the type and constraints rather than the literal syntax.

For example, tuple([string, number]) would match a value like ["a", 1], where the first item must be a string and the second a number. 

Tuples are immutable, meaning once defined, their size and element types cannot be changed, making them particularly useful for passing structured data with predictable composition between modules and resources. More precisely, Terraform enforces a fixed length and per-position types wherever a value is declared or checked as tuple, which keeps the structure predictable.

How to work with Terraform tuples

At its most basic level, a tuple in Terraform is just an ordered list of values that can include different types. For instance, let’s define a tuple containing a name, an age, and a flag indicating if the person is an admin:

locals {
  user_info = ["Alice", 29, false]
}

In this tuple, the first element is a string ("Alice"), the second is a number (29), and the third is a boolean (false). 

You can reference each value directly by its index. For example, local.user_info[0] returns "Alice", and local.user_info[2] returns false.

Terraform ensures you can’t change the structure or add different kinds of data later, which helps prevent mistakes and keeps your configurations predictable. That enforcement comes from the tuple type shape rather than a runtime mutation rule.

Converting a Terraform tuple to a list

In Terraform, tuples and lists are very similar, as both are ordered collections of values. The key difference is that a tuple can contain elements of different types, while a list generally contains elements of the same type.

You can easily convert a tuple into a list using the tolist() function. This function takes any iterable collection and converts it to a list, provided that the elements can be unified into a consistent type.

locals {
  my_tuple = ["t3.micro", "t3.small", "t3.medium"]
  my_list  = tolist(local.my_tuple)
}

In this example, my_tuple is technically a tuple because it’s defined as a literal inside a locals block. Terraform automatically treats literal lists as tuples unless they’re explicitly given a list() type. Terraform will also coerce a literal tuple to a list when the context expects list(T).

When you use tolist(local.my_tuple), it converts the tuple into a true list type. This is useful when something expects list(T) or when you use functions that return lists, such as concat. Tuples already support indexing, so you do not need to convert only for indexing.

If your tuple contains mixed types (e.g. [ "server1", 2, true ]), tolist() will fail, because Terraform can’t reconcile those into a single list element type. All elements must unify to one type for a list.

Converting a Terraform tuple to a map

To convert a tuple into a map, you need both keys and values. A tuple by itself only has ordered values, so Terraform can’t infer keys automatically, and you must provide them.

One common way to convert a tuple to a map is to use the zipmap() function, which pairs two lists: one of keys, and one of values.

For example:

locals {
  keys   = ["name", "size", "enabled"]
  values = ["db-server", "t3.medium", true]

  my_map = zipmap(local.keys, local.values)
}

Here, zipmap() combines the two tuples into a single map:

{
  "name"    = "db-server"
  "size"    = "t3.medium"
  "enabled" = true
}

This conversion is powerful because it allows you to define structured data dynamically. You can even generate the keys and values lists programmatically (for example, with for expressions) before using zipmap(). Make sure the keys and values lists are the same length.

Using a Terraform tuple with for_each

The for_each meta-argument in Terraform lets you create multiple instances of a resource or module based on a collection.

However, for_each only works with maps or sets of strings and not tuples or lists directly. If you try to use a tuple directly with for_each, Terraform will complain that it expects a map or a set. Sets are also unordered and addresses are derived from element values.

The trick is to convert the tuple to a set or a map first, depending on your use case.

Example using a set:

locals {
  instance_types = ["t3.micro", "t3.small", "t3.medium"]
}

resource "aws_instance" "example" {
  for_each = toset(local.instance_types)

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = each.value
}

In this example, toset() converts the tuple into a set of strings, which Terraform can iterate over. Each instance gets a different instance_type value based on the tuple elements.

In practice avoid hardcoding AMI IDs because they are region specific and change. Use a data source or a variable for the AMI.

If you need to refer to both keys and values, you can instead convert the tuple into a map and use that for for_each:

locals {
  instances = {
    "web"  = "t3.micro"
    "db"   = "t3.medium"
    "cache" = "t3.small"
  }
}

resource "aws_instance" "example" {
  for_each = local.instances

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = each.value
  tags = {
    Name = each.key
  }
}

This approach gives you both each.key (the label) and each.value (the instance type). It’s a best practice when you need meaningful identifiers for your resource instances. If you prefer names over positions you can use object with named attributes for readability.

Example 1: Passing a Terraform tuple to a module

In this example, we’ll show how tuples enhance type safety and modular design by being used as input variables for modules. 

Suppose you have a Terraform module that creates S3 buckets, and it needs three inputs:

  • a bucket name (string) 
  • a versioning flag (boolean)
  • a maximum object count (number)

You can define the module variable as a tuple type to ensure these values are always passed in the correct order and type.

variable "bucket_info" {
  type = tuple([string, bool, number])
}

When calling this module, you would supply the tuple like this:

module "storage" {
  source      = "./modules/storage"
  bucket_info = ["project-data", true, 5000]
}

Within the module, you can access the elements using positional indexes such as var.bucket_info[0] for the name or var.bucket_info[1] for the versioning flag.

By defining the variable type as a tuple, Terraform enforces that each element matches its expected type, which prevents configuration errors before deployment. 

Example 2: Managing security group rules using tuples

A practical use case for Terraform tuples is when you want to manage multiple network rules that share the same shape but vary by values. Instead of defining separate variables for each attribute, you can represent each ingress rule as a tuple with a fixed order. This keeps the data compact and makes the structure easy to validate.

Imagine you want to allow HTTP, HTTPS, and SSH from different sources. Each rule should include the port number, the protocol, and a CIDR block.

You can use a list of tuples to define these attributes in a structured and type safe way.

Each tuple contains three values:

  1. The port number as a number
  2. The protocol as a string
  3. The CIDR block as a string
variable "vpc_id" {
  type = string
}

variable "ingress_rules" {
  type = list(tuple([number, string, string]))
  default = [
    [80,  "tcp", "0.0.0.0/0"],
    [443, "tcp", "0.0.0.0/0"],
    [22,  "tcp", "10.0.0.0/16"]
  ]
}

resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = var.vpc_id

  dynamic "ingress" {
    for_each = {
      for idx, rule in var.ingress_rules : idx => {
        port     = rule[0]
        protocol = rule[1]
        cidr     = rule[2]
      }
    }

    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = [ingress.value.cidr]
    }
  }
}

In this configuration, Terraform uses the tuple data to generate multiple ingress rules automatically. Each tuple holds the same shape, so it is clear which value is which, and Terraform can validate the types by position. 

If you want stable addresses that do not depend on list order you can key the for_each map by a meaningful string such as port protocol and cidr combined into one value.

Key points

Tuples in Terraform are extremely flexible once you understand how to work with them.

  • You can convert a tuple to a list with tolist() if all elements share a compatible type. Conversion is needed when something expects list(T).
  • You can convert a tuple to a map using zipmap(keys, values) to pair positional data into key-value structures. Ensure both lists have the same length.
  • You can use a tuple with for_each by first converting it into a set or a map, depending on whether you just need the values or both keys and values. Remember that sets are unordered and maps provide stable keys.

Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:

  • Policies (based on Open Policy Agent)
  • Multi-IaC workflows
  • Self-service infrastructure
  • Integrations with any third-party tools

If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Discover better way to manage Terraform

Spacelift helps manage Terraform state, build more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and many more.

Learn more

Frequently asked questions

  • What is the difference between tuple and set in Terraform?

    A tuple in Terraform is a fixed-length, ordered sequence of elements where the type of each element is explicitly defined by position. A set is an unordered collection of values of the same type with no duplicates. Tuples allow mixed types, while sets require uniform types.

Article sources

Terraform Docs. Types and Values. Accessed: 14 November 2025

Terraform Docs. Type Constraints. Accessed: 14 November 2025

Terraform Commands Cheat Sheet

Grab our ultimate cheat sheet PDF
for all the Terraform commands
and concepts you need.

Share your data and download the cheat sheet