Skip to main content

Terraform Data Types

Introduction

When working with Terraform, understanding data types is essential for creating effective and error-free infrastructure as code. Terraform uses a language called HashiCorp Configuration Language (HCL) which supports various data types to represent different kinds of values. These data types form the building blocks of your infrastructure definitions, allowing you to work with everything from simple text strings to complex nested structures.

In this guide, we'll explore all the data types available in Terraform, how they work, and provide practical examples of their use in real-world scenarios.

Basic Data Types

Terraform has several primitive data types that serve as the foundation for more complex structures.

Strings

Strings are sequences of characters enclosed in quotation marks.

hcl
# String declaration
variable "instance_name" {
type = string
default = "web-server"
}

# String interpolation
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "server-${var.instance_name}"
}
}

Terraform supports several ways to define strings:

  • Double-quoted strings: Support interpolation and escape sequences.
  • Heredoc strings: For multi-line text using <<EOF and EOF syntax.
hcl
# Heredoc string example
variable "user_data" {
type = string
default = <<EOF
#!/bin/bash
echo "Hello, World!"
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
EOF
}

Numbers

Numbers in Terraform can be integers or floating-point values.

hcl
# Integer example
variable "instance_count" {
type = number
default = 5
}

# Float example
variable "timeout" {
type = number
default = 1.5 # 1.5 seconds
}

Booleans

Boolean values can be either true or false.

hcl
# Boolean example
variable "enable_monitoring" {
type = bool
default = true
}

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
monitoring = var.enable_monitoring
}

Complex Data Types

Terraform also supports composite data types that allow you to group related values together.

Lists (Arrays)

Lists are ordered collections of values of the same type.

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

# Accessing list elements
resource "aws_subnet" "example" {
vpc_id = aws_vpc.main.id
availability_zone = var.availability_zones[0] # First element
cidr_block = "10.0.1.0/24"
}

List operations include:

hcl
# List operations
locals {
first_az = var.availability_zones[0] # Element access
az_count = length(var.availability_zones) # Count elements
contains_east1a = contains(var.availability_zones, "us-east-1a") # Check if list contains value
}

Maps (Dictionaries)

Maps are collections of key-value pairs where keys are strings.

hcl
# Map declaration
variable "instance_types" {
type = map(string)
default = {
development = "t2.micro"
staging = "t2.medium"
production = "t2.large"
}
}

# Accessing map elements
resource "aws_instance" "server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_types["development"]

tags = {
Environment = "dev"
}
}

Map operations:

hcl
# Map operations
locals {
dev_instance = lookup(var.instance_types, "development", "t2.nano") # Lookup with default
all_keys = keys(var.instance_types) # Get all keys
all_values = values(var.instance_types) # Get all values
}

Sets

Sets are unordered collections of unique values of the same type.

hcl
# Set declaration
variable "allowed_ports" {
type = set(number)
default = [22, 80, 443]
}

# Using sets
resource "aws_security_group" "example" {
name = "example"
description = "Allow standard web traffic"

dynamic "ingress" {
for_each = var.allowed_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}

Set operations:

hcl
# Set operations
locals {
merged_ports = setunion(var.allowed_ports, [8080, 8443]) # Combine sets
common_ports = setintersection(var.allowed_ports, [80, 8080, 3000]) # Find common elements
}

Objects

Objects are collections of named attributes that can have different types.

hcl
# Object declaration
variable "server_config" {
type = object({
instance_type = string
ami_id = string
tags = map(string)
ebs_volumes = number
})

default = {
instance_type = "t2.micro"
ami_id = "ami-0c55b159cbfafe1f0"
tags = {
Name = "web-server"
Env = "dev"
}
ebs_volumes = 1
}
}

# Using objects
resource "aws_instance" "web" {
ami = var.server_config.ami_id
instance_type = var.server_config.instance_type

tags = var.server_config.tags

root_block_device {
volume_type = "gp2"
volume_size = 10
}
}

Tuples

Tuples are fixed-length sequences similar to lists, but can contain elements of different types.

hcl
# Tuple declaration
variable "instance_config" {
type = tuple([string, number, bool])
default = ["t2.micro", 1, true]
}

# Accessing tuple elements
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_config[0] # String: "t2.micro"
count = var.instance_config[1] # Number: 1
monitoring = var.instance_config[2] # Boolean: true
}

Type Conversions

Terraform provides several functions to convert between data types.

hcl
# Converting between types
locals {
# String to number
instance_count_str = "5"
instance_count = tonumber(local.instance_count_str)

# Number to string
port_number = 80
port_string = tostring(local.port_number)

# List to set (removes duplicates)
tags_list = ["web", "app", "web"]
unique_tags = toset(local.tags_list) # Results in ["web", "app"]

# JSON string to complex type
json_config = jsonencode({
name = "example"
port = 8080
})

# Decode JSON
parsed_config = jsondecode("{\"name\":\"example\",\"port\":8080}")
}

Type Constraints

Type constraints are used in variable declarations to ensure values conform to expected types.

hcl
# Basic type constraints
variable "server_name" {
type = string
description = "The name of the server"
}

variable "instance_count" {
type = number
description = "Number of instances to create"
default = 1
}

# Complex type constraints
variable "subnet_cidr_blocks" {
type = list(string)
description = "List of subnet CIDR blocks"
default = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "tags" {
type = map(string)
description = "Resource tags"
default = {
Environment = "dev"
Project = "example"
}
}

# Compound type constraints
variable "server_specs" {
type = object({
instance_type = string
ami_id = string
is_public = bool
volumes = list(number)
tags = map(string)
})

description = "Server specifications"
}

Conditional Expressions with Data Types

You can use different data types with conditional expressions to create dynamic configurations.

hcl
# Conditional with different data types
locals {
environment = "production"

# Conditional string
instance_size = local.environment == "production" ? "t2.large" : "t2.micro"

# Conditional with map lookup
instance_sizes = {
development = "t2.micro"
staging = "t2.medium"
production = "t2.large"
}
selected_size = lookup(local.instance_sizes, local.environment, "t2.nano")

# Conditional with list/count
replica_count = local.environment == "production" ? 3 : 1
}

resource "aws_instance" "app" {
count = local.replica_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = local.selected_size

tags = {
Name = "app-server-${count.index + 1}"
Env = local.environment
}
}

Complex Data Type Examples

Let's look at some real-world examples using complex data types:

Configuring Multiple EC2 Instances with Maps and For Each

hcl
# Define multiple server configurations
variable "server_configs" {
type = map(object({
instance_type = string
ami = string
subnet_id = string
is_public = bool
tags = map(string)
}))

default = {
"web" = {
instance_type = "t2.micro"
ami = "ami-0c55b159cbfafe1f0"
subnet_id = "subnet-12345"
is_public = true
tags = {
Role = "web"
Tier = "frontend"
}
},
"app" = {
instance_type = "t2.medium"
ami = "ami-0c55b159cbfafe1f0"
subnet_id = "subnet-67890"
is_public = false
tags = {
Role = "application"
Tier = "backend"
}
},
"db" = {
instance_type = "t2.large"
ami = "ami-0c55b159cbfafe1f0"
subnet_id = "subnet-abcde"
is_public = false
tags = {
Role = "database"
Tier = "data"
}
}
}
}

# Create instances based on the configurations
resource "aws_instance" "servers" {
for_each = var.server_configs

ami = each.value.ami
instance_type = each.value.instance_type
subnet_id = each.value.subnet_id

associate_public_ip_address = each.value.is_public

tags = merge(
each.value.tags,
{
Name = "${each.key}-server"
}
)
}

Dynamic Security Group Rules with Lists and Sets

hcl
# Define security group rules
variable "security_rules" {
type = list(object({
type = string
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
description = string
}))

default = [
{
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
description = "SSH access from internal network"
},
{
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP access from anywhere"
},
{
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
}
]
}

# Create security group with dynamic rules
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"

dynamic "ingress" {
for_each = [for rule in var.security_rules : rule if rule.type == "ingress"]
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}

dynamic "egress" {
for_each = [for rule in var.security_rules : rule if rule.type == "egress"]
content {
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
cidr_blocks = egress.value.cidr_blocks
description = egress.value.description
}
}

tags = {
Name = "web-security-group"
}
}

Data Type Best Practices

Here are some best practices to follow when working with Terraform data types:

  1. Use type constraints for all variables to catch errors early
  2. Use the simplest type that meets your needs
  3. Document complex types with descriptions and examples
  4. Use locals to simplify complex expressions and improve readability
  5. Consider validation blocks for additional type constraints beyond basic types
hcl
# Example of validation block
variable "instance_type" {
type = string
description = "EC2 instance type"
default = "t2.micro"

validation {
condition = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
error_message = "Instance type must be one of: t2.micro, t2.small, or t2.medium."
}
}

Visualizing Type Hierarchy in Terraform

Terraform's type system can be visualized as a hierarchy:



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)