TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Migrating From count to for_each Without Destroying Anything

Continue in TT Lab

Goal

You confirm the difference between count and for_each through state addresses, and learn the procedure for migrating an already applied resource to for_each without destruction.

Why it matters

count and for_each look like a choice of syntax, but in fact they are a decision about what to use as each instance's identifier. count uses order and for_each uses names as the identifier. If you delete an item from the middle of a list, the difference shows — with count, the later ones are pulled forward one slot and even resources that should have stayed are recreated, while with for_each, only that key disappears. On a real cloud, this difference shows up as "I deleted one line of configuration and two servers were recreated." When you need to change the address of an already applied resource, use a moved block. moved only moves the state and does not touch the real thing, and the refactoring history remains in the code, so others follow automatically just by running plan. There is one criterion for judging whether the migration went right — the plan must have no destruction at all.

Steps

  1. In /root/tf/foreach/main.tf, declare the local_file resource node with count = 3, run tofu init, and apply. Then, before moving addresses, save the result of tofu state list to /root/tf/foreach/out/count-state.txt. This file must contain the three lines local_file.node[0], local_file.node[1], and local_file.node[2] (it must not contain string-key forms), and it serves as the pre-migration snapshot for step 7.
  2. Make the file name of node /root/tf/foreach/out/node-${count.index}.txt and its contents node-${count.index}. Do not write the name by hand three times; you must build it from the repetition index. There must be exactly 3 node-*.txt files under out, and the first lines of the files must be node-0, node-1, and node-2.
  3. Make the local_file resource zone with for_each = toset(["kr", "us", "eu"]) and have three files /root/tf/foreach/out/zone-<키>.txt created (where the placeholder is the key). The keys in the state must be eu, kr, and us.
  4. Iterate the local_file resource service over a map. The keys are api, web, and worker, and each value holds a port (the port of web must be 8080). Inside /root/tf/foreach/out/service-web.txt, name=web and port=8080 must each be on its own line.
  5. With a for expression, define an output service_ports that has the service name as the key and the port as the value, and save the result of tofu output -json to /root/tf/foreach/out/outputs.json. There must be 3 entries, and web must be 8080.
  6. Create variable "enable_debug" (bool, default false) and use count = var.enable_debug ? 1 : 0 on the local_file resource debug. The file path is /root/tf/foreach/out/debug.txt and the contents are free (the reason you may keep using count is that a switch has at most one instance, so there is no chance of the index shifting). Save the output of the plan with the switch turned on (tofu plan -var enable_debug=true) to /root/tf/foreach/out/toggle.txt (it must show 1 to add), but do not apply it. In the final state there must be 0 debug instances and /root/tf/foreach/out/debug.txt must not exist either.
  7. Change node to for_each = { n0 = 0, n1 = 1, n2 = 2 }. The map's keys are for the new addresses and the values are the old indexes, so you must build the file name and contents from each.value, not each.key, and keep node-0.txt, node-1.txt, node-2.txt and their contents exactly the same as in steps 1–2 (if you change the names, it becomes recreation rather than migration and destruction shows up in the plan). And connect the old addresses and new addresses with 3 moved blocks, such as moving local_file.node[0] to local_file.node["n0"]. After you apply, if you save the tofu plan output to /root/tf/foreach/out/after-plan.txt, it must be No changes with no destruction at all.
  8. Create /root/tf/foreach/out/addresses.json. Put the addresses from tofu state list as an array under the addresses key (node 3 + zone 3 + service 3 = 9 or more), and there must be no address with a numeric index. Write the number of numeric-index addresses in the numeric_index_count key, which is 0 if the migration is finished.

Notes

Create three with count and record the addresses before moving

In /root/tf/foreach/main.tf, declare the local_file resource node with count = 3, run tofu init, and apply. Then, before moving addresses, save the result of tofu state list to /root/tf/foreach/out/count-state.txt. This file must contain the three lines local_file.node[0], local_file.node[1], and local_file.node[2] (it must not contain string-key forms), and it serves as the pre-migration snapshot for step 7.

If you put count = 3 in the resource block, three instances are created. You will move the addresses later, so keep the current address list in a file — without a record from before the move, you cannot prove what moved and how.

Distinguish instances with count.index

Make the file name of node /root/tf/foreach/out/node-${count.index}.txt and its contents node-${count.index}. Do not write the name by hand three times; you must build it from the repetition index. There must be exactly 3 node-*.txt files under out, and the first lines of the files must be node-0, node-1, and node-2.

Each instance needs a different file name, so insert the index into the name. Distinguish the contents in the same way.

Use for_each over a set

Make the local_file resource zone with for_each = toset(["kr", "us", "eu"]) and have three files /root/tf/foreach/out/zone-<키>.txt created (where the placeholder is the key). The keys in the state must be eu, kr, and us.

You cannot pass a list directly to for_each. You must convert it to a set, and then the key becomes the element value itself.

for_each over a map and each.value

Iterate the local_file resource service over a map. The keys are api, web, and worker, and each value holds a port (the port of web must be 8080). Inside /root/tf/foreach/out/service-web.txt, name=web and port=8080 must each be on its own line.

When you pass a map, each.key is the map's key and each.value is its value. If the value is an object, you can dig into its fields.

Build an output map with a for expression

With a for expression, define an output service_ports that has the service name as the key and the port as the value, and save the result of tofu output -json to /root/tf/foreach/out/outputs.json. There must be 3 entries, and web must be 8080.

for_each is a meta-argument that multiplies resources, and for is an expression that turns a value into another shape. Use the form { for k, v in ... : k => ... }.

Make a feature switch with a conditional count

Create variable "enable_debug" (bool, default false) and use count = var.enable_debug ? 1 : 0 on the local_file resource debug. The file path is /root/tf/foreach/out/debug.txt and the contents are free (the reason you may keep using count is that a switch has at most one instance, so there is no chance of the index shifting). Save the output of the plan with the switch turned on (tofu plan -var enable_debug=true) to /root/tf/foreach/out/toggle.txt (it must show 1 to add), but do not apply it. In the final state there must be 0 debug instances and /root/tf/foreach/out/debug.txt must not exist either.

있으면 1, 없으면 0 is the on/off idiom (1 if present, 0 if absent). Check the turned-on state only with a plan and do not actually apply it.

Migrate count to for_each with a moved block

Change node to for_each = { n0 = 0, n1 = 1, n2 = 2 }. The map's keys are for the new addresses and the values are the old indexes, so you must build the file name and contents from each.value, not each.key, and keep node-0.txt, node-1.txt, node-2.txt and their contents exactly the same as in steps 1–2 (if you change the names, it becomes recreation rather than migration and destruction shows up in the plan). And connect the old addresses and new addresses with 3 moved blocks, such as moving local_file.node[0] to local_file.node["n0"]. After you apply, if you save the tofu plan output to /root/tf/foreach/out/after-plan.txt, it must be No changes with no destruction at all.

When an address changes, the tool reads it as a destroy and a create. Connect the old and new addresses with moved, and keep the file names and contents unchanged so that there is no recreation.

Create the full address list and the migration completion report

Create /root/tf/foreach/out/addresses.json. Put the addresses from tofu state list as an array under the addresses key (node 3 + zone 3 + service 3 = 9 or more), and there must be no address with a numeric index. Write the number of numeric-index addresses in the numeric_index_count key, which is 0 if the migration is finished.

Turn the state list into a JSON array and put it in. Count for yourself whether any numeric-index address remains.