IaCInfrastructure as Codeterraformterraform methods

Terraform Features I Use Every Day (for_each, count, dynamic, locals, depends_on, lifecycle, ...)

Learn the Terraform features you'll use in almost every real-world project, including for_each, count, locals, dynamic blocks, depends_on, and lifecycle.

Jul 22, 20265 min readUpdated Jul 22, 2026
Terraform Features I Use Every Day (for_each, count, dynamic, locals, depends_on, lifecycle, ...)

Terraform Features I Use Every Day

In the previous articles, we've learned:

  • How Terraform works
  • What Terraform State is
  • Variables and Outputs
  • Modules
  • Data Sources

At this point, you already know enough to build a basic Terraform project.

However, if you browse production Terraform projects on GitHub or within enterprise codebases, you'll frequently encounter syntax like:

code
for_each = ...
 
count = ...
 
locals {
...
}
 
dynamic "ingress" {
...
}
 
lifecycle {
...
}
 
depends_on = [...]

For newcomers, Terraform can suddenly feel overwhelming.

"Why does Terraform have so many keywords?"

Fortunately, it doesn't.

Terraform actually has a very small language.

In reality, there are only six or seven core features that appear in almost every project.

Once you understand them, you'll be able to read most Terraform code you'll encounter in the real world.


Terraform Is Not a Programming Language

This is an important mindset shift.

Terraform is not Java.

It's not Go.

It's not Python.

Terraform doesn't have:

  • classes
  • functions
  • traditional loops
  • full if/else statements

Terraform is a declarative language.

Instead of writing instructions step by step, you simply describe:

"This is what my infrastructure should look like."

Terraform figures out how to get there.

Because of that, features like:

  • for_each
  • count
  • dynamic

aren't loops in the traditional programming sense.

They simply allow Terraform to generate multiple resources from a single template.


The Features You'll Use Most Often

Across nearly every Terraform project I work on, these are the features I use repeatedly.

FeaturePurpose
localsCreate intermediate values
for_eachCreate multiple resources from a map or set
countCreate multiple resources by quantity
dynamicGenerate nested blocks
depends_onManually define dependencies
lifecycleControl how Terraform updates resources

If you're comfortable with these six features, you'll already understand 80–90% of production Terraform code.


locals — Intermediate Variables

Suppose multiple resources share the same naming prefix.

Instead of repeating it everywhere:

code
resource "aws_s3_bucket" "logs" {
  bucket = "company-dev-logs"
}
 
resource "aws_s3_bucket" "backup" {
  bucket = "company-dev-backup"
}

You can define it once:

code
locals {
  prefix = "company-dev"
}
 
resource "aws_s3_bucket" "logs" {
  bucket = "${local.prefix}-logs"
}
 
resource "aws_s3_bucket" "backup" {
  bucket = "${local.prefix}-backup"
}

Then simply reference:

code
local.prefix

wherever you need it.


locals Are Not Variables

This is one of the most common misconceptions.

A variable:

code
variable "environment" {}

is provided from outside Terraform.

For example:

code
terraform apply \
-var="environment=dev"

A local, however, is computed inside Terraform.

code
locals {
    prefix = "company-${var.environment}"
}

Think of it this way:

  • Variables are inputs.
  • Locals are calculated values derived from those inputs.

count — Create Multiple Resources by Quantity

Suppose you need three identical EC2 instances.

code
resource "aws_instance" "web" {
    count = 3
 
    instance_type = "t3.micro"
}

Terraform creates:

web[0]

web[1]

web[2]

You can reference them like this:

code
aws_instance.web[0]

When Should You Use count?

count works well when:

  • every resource is identical
  • only the index differs

Examples include:

  • identical virtual machines
  • identical disks
  • identical worker nodes

The Downsides of count

Suppose you start with:

count = 3

Terraform creates:

0

1

2

Later you change it to:

count = 2

Terraform removes:

2

The real problem appears when the order of your input changes.

Because resources are identified by numeric indexes, Terraform may think existing resources have changed when they've merely shifted positions.

This is why modern Terraform projects generally prefer for_each whenever possible.


for_each — The Most Common Way to Create Multiple Resources

Consider this example:

code
locals {
    buckets = {
        logs = "company-logs"
        backup = "company-backup"
        assets = "company-assets"
    }
}

Then:

code
resource "aws_s3_bucket" "bucket" {
    for_each = local.buckets
 
    bucket = each.value
}

Terraform creates:

bucket["logs"]

bucket["backup"]

bucket["assets"]

each.key vs each.value

For example:

code
locals {
    buckets = {
        logs = "company-logs"
    }
}

Then:

code
each.key

returns:

logs

while:

code
each.value

returns:

company-logs

Why Is for_each Better Than count?

Suppose your resources are:

logs

backup

assets

Later, you add:

media

Terraform only creates:

media

The existing resources remain untouched.

Nothing gets renumbered.

That's why for_each has become the preferred approach in most production Terraform projects.


dynamic Blocks — Generate Nested Blocks

Some resources contain repeated nested blocks.

For example:

code
resource "aws_security_group" "web" {
 
    ingress {
        ...
    }
 
    ingress {
        ...
    }
 
    ingress {
        ...
    }
}

If you have dozens of rules, this quickly becomes difficult to maintain.

Terraform provides:

code
dynamic "ingress" {
 
}

For example:

code
dynamic "ingress" {
    for_each = var.ingress_rules
 
    content {
        from_port = ingress.value.port
        to_port = ingress.value.port
        protocol = "tcp"
        cidr_blocks = ingress.value.cidr
    }
}

Terraform automatically generates multiple:

ingress {}

ingress {}

ingress {}

blocks for you.


depends_on — Manual Dependencies

Most of the time, Terraform automatically determines resource dependencies.

For example:

code
resource "aws_instance" "web" {
    subnet_id = aws_subnet.private.id
}

Terraform understands:

Subnet

↓

EC2

without any additional configuration.


When Do You Need depends_on?

Sometimes dependencies aren't obvious.

For example:

code
depends_on = [
    aws_iam_role.example
]

Terraform will always create the IAM Role first.

Or perhaps:

CloudFront

↓

Route53

even though neither resource directly references the other.


Don't Overuse depends_on

Many beginners add:

code
depends_on

to nearly every resource.

This usually makes Terraform's dependency graph more complicated than necessary.

Let Terraform infer dependencies whenever possible.

Only use depends_on when Terraform genuinely cannot determine the relationship itself.


lifecycle — Control Terraform's Behavior

One of Terraform's most powerful features is:

code
lifecycle {
 
}

which provides several useful options.


create_before_destroy

By default, Terraform typically performs:

Destroy

↓

Create

This can introduce downtime.

Instead:

code
lifecycle {
    create_before_destroy = true
}

Terraform changes the order to:

Create

↓

Destroy

This is particularly useful when replacing:

  • Load Balancers
  • Launch Templates
  • Auto Scaling Groups
  • DNS resources

prevent_destroy

For example:

code
lifecycle {
    prevent_destroy = true
}

If someone accidentally runs:

code
terraform destroy

Terraform refuses to delete the resource.

This is extremely valuable for critical infrastructure such as:

  • Production databases
  • KMS keys
  • Data buckets
  • DNS zones

ignore_changes

Example:

code
lifecycle {
    ignore_changes = [
        tags
    ]
}

Terraform ignores changes to the tags attribute.

This is useful when:

  • another system manages tags
  • Azure Policy automatically adds tags
  • AWS Organizations injects metadata

Without ignore_changes, every terraform plan would show unnecessary drift.


The Features I Use Most Often

Across my own Terraform projects, the usage frequency looks roughly like this:

FeatureFrequency
locals⭐⭐⭐⭐⭐
for_each⭐⭐⭐⭐⭐
lifecycle⭐⭐⭐⭐
data sources⭐⭐⭐⭐
dynamic⭐⭐⭐
depends_on⭐⭐
count⭐⭐

In particular,

  • locals
  • for_each

appear in almost every module I write.


Summary

Terraform isn't a large language.

Most production code revolves around a handful of powerful features:

  • locals to eliminate duplication
  • for_each to create stable collections of resources
  • count for simple repeated resources
  • dynamic to generate nested blocks
  • depends_on when Terraform cannot infer dependencies
  • lifecycle to control how resources are created, updated, or destroyed

Once you're comfortable with these features, you'll be able to understand the majority of Terraform modules you find on GitHub or inside enterprise codebases.

In the next article, we'll move on to another important topic:

How should you organize a Terraform project as the number of resources keeps growing? In the next article, we'll explore how to structure modules, organize directories, and design a Terraform repository that is scalable, maintainable, and production-ready.

Comments

0/2000

Loading comments…