CTCaliberTek
Course: Infrastructure as Code and Monitoring · Lesson 4 of 6Log in to track your progress and earn a certificate when you finish the course.
DevOps

Terraform Modules, count and for_each, Import, and Moved Blocks

Published September 23, 2026·Updated October 29, 2026·10 min read
Like SaveLog in to like, save, and track progress.

Introduction

As configurations grow you need to reuse code, create many similar resources, bring existing infrastructure under management, and rename things without destroying them. Modules, loops, import blocks, and moved blocks solve these problems. This final Terraform lesson covers each.

Prerequisites

  • The previous three Terraform lessons

Step-by-Step Guide

Step 1: Create a module

A module is a folder of Terraform files that you call from another configuration. The folder where you run Terraform is the root module, and any module it calls is a child module. This small module writes a greeting file and returns its path.

# modules/greeting/main.tf
variable "name" {
  type        = string
  description = "Who to greet"
}

resource "local_file" "greeting" {
  filename = "${path.root}/${var.name}.txt"
  content  = "Hello, ${var.name}!\n"
}

output "path" {
  value = local_file.greeting.filename
}

Step 2: Call the module

Use a module block with a source and the module's inputs, and read its outputs as module.NAME.OUTPUT. After adding a module or changing its source, run terraform init again. For modules from the public registry, pin an exact version, because modules change over time. The version argument applies to registry modules only.

module "alice" {
  source = "./modules/greeting"
  name   = "alice"
}

output "alice_file" {
  value = module.alice.path
}

# A registry module, pinned:
# module "vpc" {
#   source  = "terraform-aws-modules/vpc/aws"
#   version = "6.0.1"
# }

Step 3: Create many resources with count and for_each

count makes a number of near-identical copies, addressed as resource[0], resource[1], and so on. for_each takes a map or a set of strings and gives each instance a stable key, which is safer when items are added or removed in the middle. A list must be converted with toset. You cannot use both on the same resource. A condition with count set to 0 or 1 creates a resource only when needed.

resource "local_file" "numbered" {
  count    = 3
  filename = "${path.module}/file-${count.index}.txt"
  content  = "This is file number ${count.index}"
}

variable "names" {
  type    = set(string)
  default = ["alpha", "beta"]
}

resource "local_file" "named" {
  for_each = var.names
  filename = "${path.module}/${each.key}.txt"
  content  = "Hello, ${each.key}"
}

Step 4: Adopt existing infrastructure with import

If something was created outside Terraform, you can bring it under management. An import block tells the next apply to import a real object into a resource address in your configuration. Write the matching resource block so Terraform knows how to manage it, then plan and review carefully. The same idea works for any provider, and the example below uses an AWS bucket as an illustration.

import {
  to = aws_s3_bucket.this
  id = "my-existing-bucket"
}

resource "aws_s3_bucket" "this" {
  bucket = "my-existing-bucket"
}

Step 5: Refactor safely with moved blocks and good habits

Renaming a resource in code looks like a delete and a create to Terraform. A moved block records the rename so state is updated and nothing is destroyed. Keep files organized as main.tf, variables.tf, outputs.tf, and providers.tf, run fmt and validate before committing, pin Terraform, provider, and module versions, and never hard-code secrets.

moved {
  from = local_file.greeting
  to   = local_file.welcome
}

resource "local_file" "welcome" {
  filename = "${path.module}/hello.txt"
  content  = "Hello!\n"
}

FAQs

count or for_each?
Use count for a number of identical copies or an on/off switch. Use for_each when each instance is distinct, because removing an item from a count list shifts every later index and can cause unwanted changes.
When should I write a module instead of repeating code?
When you use the same group of resources more than once or want a clean interface with inputs and outputs. Avoid over-engineering: a module used once is often just indirection.

Summary

Modules package reusable code, count and for_each create many instances, import blocks adopt existing resources, and moved blocks refactor without destruction. Pin versions and keep a consistent file layout.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.