linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-beginner-08-count

lesson ── terraform-beginner ── ~12 мин ── 3 шагов

count vs for_each: creating resources in bulk

Sometimes you need N identical resources: three buckets for different regions, five security groups for different ports. Copying resource blocks by hand is painful and fragile. Terraform handles this with count and for_each.

These two tools look alike, but there is an important difference that surfaces when the list changes. See tf-count-for-each.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Create three buckets with count

    Create main.tf:

    hcl
    resource "aws_s3_bucket" "many" {
      count  = 3
      bucket = "linuxlab-count-${count.index}-${random_id.suffix.hex}"
      tags = {
        Index = tostring(count.index)
      }
    }
    resource "random_id" "suffix" {
      byte_length = 4
    }

    Here:

    • count = 3, create three instances.
    • count.index, the index of the current one (0, 1, 2).
    • tostring(count.index), convert the number to a string for the tag.
    bash
    cd /home/student/tf-count
    terraform init -input=false
    terraform apply -auto-approve -input=false

    In state you will see:

    aws_s3_bucket.many[0]
    aws_s3_bucket.many[1]
    aws_s3_bucket.many[2]
    подсказка

    If apply complains "duplicate bucket name": random_id.suffix needs to be substituted in. Check that the random_id resource is present in the HCL.

    ✓ Three buckets with indexes 0, 1, 2: count works.

  2. 02

    Add three buckets with for_each

    Add one more resource to main.tf:

    hcl
    resource "aws_s3_bucket" "regional" {
      for_each = toset(["us", "eu", "ap"])
      bucket = "linuxlab-regional-${each.key}-${random_id.suffix.hex}"
      tags = {
        Region = each.key
      }
    }

    Here:

    • for_each = toset([...]), create one for each element of the set.
    • each.key, the current key ("us", "eu" or "ap").
    • each.value, the same thing for a set; for a map it differs.
    bash
    terraform apply -auto-approve

    State gains:

    aws_s3_bucket.regional["us"]
    aws_s3_bucket.regional["eu"]
    aws_s3_bucket.regional["ap"]

    These are not indexes, they are named keys. Much more stable when the list changes.

    подсказка

    toset is required: for_each does not accept a plain list, only a set or a map. Drop the toset and it will fail.

    ✓ for_each created three buckets keyed by us, eu, ap.

    The same thing on OpenTofu

    OpenTofu keeps the CLI and state compatible with Terraform for the commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. On a first switch, though, make a backup of the state and do a run on a feature branch, the differences cluster in the newer features (variables in backend, state encryption, OCI registry-backed modules). See tf-opentofu-parity for the full matrix.

    • → OpenTofu parity
  3. 03

    Remove the middle element from both lists

    Right now you have 6 buckets: 3 from count, 3 from for_each. Let's remove the middle one in each case. Edit the HCL:

    hcl
    resource "aws_s3_bucket" "many" {
      count  = 2   # was 3
      bucket = "linuxlab-count-${count.index}-${random_id.suffix.hex}"
      tags = {
        Index = tostring(count.index)
      }
    }
    resource "aws_s3_bucket" "regional" {
      for_each = toset(["us", "ap"])   # removed "eu"
      bucket   = "linuxlab-regional-${each.key}-${random_id.suffix.hex}"
      tags = {
        Region = each.key
      }
    }

    Run plan:

    bash
    terraform plan

    What you will see:

    • count: Plan: 0 to add, 0 to change, 1 to destroy. It removes aws_s3_bucket.many[2]. That is the last one by index, not the middle. If you actually wanted the middle one gone, count does not "understand" that.
    • for_each: Plan: 0 to add, 0 to change, 1 to destroy. It removes exactly aws_s3_bucket.regional["eu"]. Precisely the one you took out. us and ap are untouched.

    This is the key difference: for_each is more stable.

    bash
    terraform apply -auto-approve
    подсказка

    If plan shows destroy + create for count: that is expected, count.index shifts. For middle deletes, switch to for_each.

    ✓ Removed 2 buckets (one from each block), 4 remain. for_each did it cleanly.

    How to choose between count and for_each

    If the resources really are identical and the count only grows or shrinks from the end, count is fine. If each one has its own meaning (region, name, environment): for_each. When in doubt, take for_each. Refactoring count to for_each is a painful cost: you need moved {} blocks or a manual state mv. Better to do it right from the start.

    • → count vs for_each
    • → Resource addressing

Что ты узнал

You created three buckets with count (addressed by index [0], [1], [2]) and three with for_each (addressed by key ["us"], ["eu"], ["ap"]). You saw that for_each is more stable when the list changes, it does not shift all the other indexes.

команды

  • terraform state listsee the indexes and keys of every element
  • terraform state show 'aws_s3_bucket.regional["us"]'one item from for_each
  • terraform plan -target='aws_s3_bucket.regional["eu"]'a targeted plan for a single element

концепции

  • · count = identical resources, addressed by index 0..N-1
  • · for_each = resources by key, addressed by string
  • · Removing the middle of a list: count recreates everything after it, for_each does not

← предыдущий

Capstone, VPC + ALB + ECS Fargate + Lambda

следующий →

Declarative import: capturing an existing resource

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies