Skip to main content
When you need the same type of resource with slightly different names or configs — three S3 buckets for different purposes, four subnets across availability zones, a set of SSM parameters — writing a separate unit block for each is repetitive and brittle. Add one more bucket to the list and you have to add another block. for_each over a list solves this: ubx creates one resource per element, making the list the authoritative source of what gets provisioned. The current element is always available as each.value, and the resource label becomes the iteration key in the generated Pulumi state. The result is a concise declaration that scales from two resources to twenty without touching the block definition.

What you’ll learn

  • How for_each over a list creates one resource per element
  • How to use each.value to reference the current element
  • How Pulumi keys the generated resources in state

Why this matters

With for_each, your list is the single source of truth. Adding or removing an element automatically provisions or destroys the corresponding resource on the next ubx apply — no block duplication, no manual bookkeeping.

The source code

# for_each over a list — creates one resource per item.
# each.value is the current list element.
unit "aws_s3_bucket_v2" "bucket" {
  for_each = ["assets", "logs", "uploads", "backups"]
  bucket   = "${input.env}-${each.value}"

  tags = {
    Name    = "${input.env}-${each.value}"
    Purpose = each.value
    Env     = input.env
  }
}

How it works

1

The for_each list is resolved at compile time

The list ["assets", "logs", "uploads", "backups"] is Resolved<T> — it’s a literal known at compile time. ubx expands it into four IRUnit nodes in the IR pass, one per element.
2

each.value is substituted per iteration

In each expanded node, each.value is replaced with the corresponding list element: "assets", "logs", "uploads", "backups". The bucket name and tag values are computed for each iteration.
3

Pulumi keys resources by label and index

The generated code creates four separate BucketV2 instances. Pulumi tracks each one in state using the resource label (bucket) combined with the element value as the key — so a rename in the list triggers a destroy and recreate, not an in-place update.

What ubx generates

// Generated by ubx — do not edit
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const env = config.get("env") || "dev";

const bucketList = ["assets", "logs", "uploads", "backups"];
const buckets = Object.fromEntries(
    bucketList.map(item => [item, new aws.s3.BucketV2(`bucket-${item}`, {
        bucket: `${env}-${item}`,
        tags: { Name: `${env}-${item}`, Purpose: item, Env: env },
    })])
);

export const envOut = env;

Common mistakes

Renaming an element in the for_each list is a destroy-and-recreate, not a rename. Pulumi keys resources by the element value — changing "assets" to "static" means destroying the assets bucket and creating a new static bucket. Use the moved block if you need to rename without destroying.
for_each over a list works best when all elements are the same “shape” — the same attributes with the element value substituted in. If different elements need significantly different configurations, consider separate unit blocks with when conditions, or for_each over an object with richer per-key values.

Run it

ubx validate
ubx plan             # creates 4 buckets
ubx plan --var env=prod

What you learned

for_each over a list creates one resource per element, with each.value as the current item
The list is the authoritative source — add or remove elements to provision or destroy resources
Renaming a list element triggers destroy-and-recreate; use moved for safe renames

Next steps

for_each over an object

Use each.key and each.value with a map

count meta-argument

Create N copies of a resource by number