terraform metaarguments

See

Core Idea

Meta-arguments bend Terraform’s one-block-one-object rule: count and for_each multiply resources, depends_on orders them, and lifecycle tunes their behavior.

  • What meta-arguments are, then each one: depends_on, count, for_each, lifecycle (resource behaviour and lifecycle rules).
  • Use cases and a count versus for_each comparison.
  • alias for multi-instance resources, shown with primary and secondary region S3 buckets.

See

In Terraform a single resource block represents a single infrastructure object. But what if we want to create multiple near-identical infrastructure objects without having to copy-paste the resource block multiple times.

This is where the Terraform count meta-argument comes into the picture

What is meta-arguments in Terraform

Important

Terraform language defines several meta-arguments, which can be used with any resource type to change the behavior of resources.

Terraform defines meta-arguments as arguments that can be used with every resource type to change the resource’s behavior. Terraform supports the following meta-arguments:

  • depends_on - for specifying explicit dependencies
  • count - for creating multiple resource instances according to a count
  • for_each - to create multiple instances according to a map, or set of strings
  • provider - for selecting a non-default provider configuration
  • lifecycle - for lifecycle customization
  • provisioner - for taking extra actions after resource creation

depends_on

count

When you are managing a pool of objects eg. a fleet of Virtual Machines you can use count.
Specify the amount of instances you want
Get the current count value (index) via count.index

This value starts at 0

Count can accept numeric expressions:

  • Must be whole number
  • Number must be known before configuration

for_each

resource "aws_instance" "my_server" {
	for_each = {
		nano = "t2.nano"
		micro = "t2.micro"
		small = "t2.small"
	}
	ami = "ami-d75d26526cbca76a"
	instance_type = each.value
	tags = {
		Name = "Server-${each.key}"
	}
}

lifecycle

Resource Behaviour

Lifecycle

Lifecycle block allows you to change what happens to resources e.g. create, update, destroy.
Lifecycle blocks are nested within resources

  1. create_before _destroy (bool)
    When replacing a resource first create the new resource before deleting it (the default is destroy old first)

  2. prevent_destroy (bool)
    Ensures a resource is not destroyed

  3. ignore_changes (list of attributes)
    Don’t change the resource (create, update, destroy) if a change occurs for the listed attributes.

...
resource "aws_s3_bucket" "example" {
  bucket = "example-lifecycle-bucket"
  acl    = "private"
 
  lifecycle {
    prevent_destroy = true # Prevent accidental bucket deletion
    ignore_changes = [acl] # Ignore changes to the ACL attribute
  }
 
  tags = {
    Name        = "lifecycle-example-s3-bucket"
    Environment = "Dev"
  }
}
...

Use Cases

  1. Simple provisioning of multiple resources of the same kind
resource "aws_instance" "this" {
   count         = 3
   instance_type = "t2.micro"
   ami           = "my_ami_id"
}

This will create three AWS EC2 instances.

  1. Conditional creation of resources
resource "aws_instance" "this" {
   count         =  var.create_instance ? 1 : 0
   instance_type = "t2.micro"
   ami           = "my_ami_id"
}
 
variable "create_instance" {
 type    = bool
 default = true
}

This will create an EC2 instance if the create_instance variable is set to true.

  1. Scaling resources dynamically using a variable
resource "aws_instance" "this" {
   count         =  var.instance_number
   instance_type = "t2.micro"
   ami           = "my_ami_id"
}
 
variable "instance_number" {
 type    = number
 default = 5
}
  1. Iterating over lists and creating multiple resources of the same kind
resource "aws_instance" "this" {
   count         = length(var.instances)
   instance_type = var.instances[count.index].instance_type
   ami           = var.instances[count.index].ami
}
 
variable "instances" {
 type    = list(object({
   instance_type = string
   ami           = string
 }))
 default = [{
   ami           = "ami1"
   instance_type = "t2.micro"
 },
 {
   ami           = "ami2"
   instance_type = "t3.micro"
 },
 ]
}
  1. How to use Terraform count with conditional expressions?
locals {
 server_names=["backend-service-a", "backend-service-b", "backend-service-c"]
}
 
resource "aws_instance" "backend_server" {
 ami           = "ami-07355fe79b493752d"
 instance_type = "t2.micro"
 count         = length(local.server_names)
 tags          = {
   Name = local.server_names[count.index]
 }
}
  1. Conditional expressions with Terraform count=0

You can conditionally create resources in Terraform by leveraging count (for_each too). For any resource, if a certain condition is met, we will add a count of one, and if it is not met, we will add a count of zero, which means that the resource is not created.

resource "any_resource" "any_name" {
   count      = my_condition ? 1 : 0
   parameter1 = "this"
   ...
}

Count vs For Each

TypeDescriptionUse case
countMeta-argumentBased on a count valueResources you are provisioning are identical
for_eachMeta-argumentBased on a set of input valuesResources change between the different instances

Alias

The alias argument in Terraform allows you to define multiple instances of the same provider to configure resources in different ways. This is especially useful when working with multiple regions, accounts, or configurations in AWS or other providers.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }
}
 
provider "aws" {
  region = var.primary_region
}
 
provider "aws" {
  alias  = "secondary"
  region = var.secondary_region
}
 
# Create an S3 bucket in the primary region
resource "aws_s3_bucket" "primary" {
  provider = aws
  bucket   = "example-primary-region-bucket-${random_pet.primary_suffix.id}"
  acl      = "private"
 
  tags = {
    Name        = "Primary S3 Bucket"
    Environment = "Dev"
  }
}
 
# Create an S3 bucket in the secondary region
resource "aws_s3_bucket" "secondary" {
  provider = aws.secondary
  bucket   = "example-secondary-region-bucket-${random_pet.secondary_suffix.id}"
  acl      = "private"
 
  tags = {
    Name        = "Secondary S3 Bucket"
    Environment = "Dev"
  }
}
...