The moved block is a mechanism that declaratively reflects address changes (resource/module addresses) in state without recreating the underlying infrastructure.
Its biggest advantage is preventing procedural mistakes and environment drift during team development and CI/CD-driven refactoring.
When you refactor and change resource names or module structure, the Terraform address changes too. Without intervention, plan shows a destroy for the old resource and a create for the new one. When you need to preserve existing resources and avoid downtime, declaring the old → new address mapping in a moved block lets apply rewire only the state binding while leaving the physical resource intact.
In practice, this comes up frequently with naming convention changes, modularization and monorepo restructuring, and migrations from count to for_each. Compared to manually editing state via commands, leaving the change history in code gives you better reproducibility, reviewability, and safety in CI.
Minimal example: renaming a resource (state moves only, the entity is unchanged)
resource "aws_s3_bucket" "app_primary" {
# Old: aws_s3_bucket.app
# Configuration is unchanged
}
moved {
from = aws_s3_bucket.app
to = aws_s3_bucket.app_primary
}The moved block is available in Terraform 1.1 and later. The syntax is moved { from = <old address> to = <new address> }. The from and to must point to state objects of the same kind (same resource type, same managed granularity). Moves across types or providers are not allowed.
Place the moved block in a module that can reference both the from and to addresses. A safe rule of thumb is to use the smallest common parent module of the two. For example, when moving a root-level resource into a child module, write the moved block in the root module — from points to the root resource, and to uses the module.<name>.<resource> form.
The preconditions: the to-side resource (or module) must exist in code as the new configuration but not yet be registered in state; the from-side object must exist in state but no longer exist in code (or no longer be referenced after the rename). It shows up in plan as a moved entry and is consumed exactly once during apply.
Move accompanying modularization (root → child module)
# root module
module "storage" {
source = "./modules/storage"
}
# Old: aws_s3_bucket.app (existed at root)
# New: module.storage.aws_s3_bucket.app (defined inside the child module)
moved {
from = aws_s3_bucket.app
to = module.storage.aws_s3_bucket.app
}Here are the three most common patterns. In every case, first prepare the new definition (the to-side resource/module) in code, add the moved block at the same time, and confirm that plan shows only moved entries.
For count → for_each, every instance address changes, so you list as many moved blocks as needed. The key is to fix a one-to-one mapping from index numbers to key names.
Visualizing the module move
Examples of moved blocks by case
# 1) Rename a resource (within the same module)
resource "aws_s3_bucket" "app_primary" {}
moved {
from = aws_s3_bucket.app
to = aws_s3_bucket.app_primary
}
# 2) Move from root → child module (declared at root)
module "storage" { source = "./modules/storage" }
moved {
from = aws_iam_role.app
to = module.storage.aws_iam_role.app
}
# 3) count → for_each (map index → key individually)
# Old: aws_instance.web[count = 2]
# New: aws_instance.web[for_each = {"a" = ..., "b" = ...}]
moved {
from = aws_instance.web[0]
to = aws_instance.web["a"]
}
moved {
from = aws_instance.web[1]
to = aws_instance.web["b"]
}Several operations resemble an address move. Pick based on intent. If you want to preserve existing resources and have the change reproducible across the team, moved is the first choice. For one-off emergency fixes or correcting historical drift, consider state commands. To pull in pre-existing external resources, import-family options are the right fit.
| Mechanism | Nature of the Operation | Shareability / Reproducibility | Impact Scope |
|---|---|---|---|
| moved block | Declarative; moves state exactly once during apply | High (lives in code) | Non-destructive (physical resource preserved) |
| terraform state mv | Imperative; edits state immediately | Low (depends on manual operation) | Non-destructive (but leaves room for human error) |
| import (import block / terraform import) | Pulls existing resources into state | Medium (the block form can be codified) | Non-destructive (intake only) |
| replace (-replace or lifecycle) | Explicitly recreates the resource | Medium (mix of command and code) | Destructive (recreation) |
Treat "adding the new definition" and "adding the moved block" as a single change, confirm that plan shows no create/destroy, then apply. Enable remote backend locking and roll out environment by environment.
When batching multiple moved blocks, verify in plan that every entry shows up as moved as expected. If the to-side already exists in state you will get an error, so resolve duplicates first (with state rm or destroy) before proceeding.
Expected plan output (conceptual example)
Terraform will perform the following actions:
# aws_s3_bucket.app_primary has moved to aws_s3_bucket.app_primary
moved from aws_s3_bucket.app
to aws_s3_bucket.app_primary
Plan: 0 to add, 0 to change, 0 to destroy. 2 to move.Exams test you on choosing between moved, import, replace, and state mv; where to place the moved block; per-instance moves for count → for_each; the to/from preconditions; and what is not supported (data). It is also important to be able to read plan output (moved appears, no create/destroy).
Also remember: moved cannot be used across types or providers; apply fails if the to-side is already in state; and since moved is consumed once after apply, the recommended practice is to apply across every environment before deleting it.
Pro
問題 1
You have aws_s3_bucket.web in the root module and want to move it to aws_s3_bucket.web_main inside the storage child module — without recreation. Which placement and definition of the moved block is correct?
正解: A
Place moved in the smallest common parent module that can reference both from and to — here that is the root. data is not eligible, and state mv is imperative rather than declarative, with low reproducibility.
When can I safely delete a moved block?
Delete it once the moved block has been consumed in every target environment (dev, staging, prod, etc.) and the state move is complete. Leaving it in place for too long becomes noise, so cleaning it up at the end of the migration is the recommended practice.
The target address already exists in state and apply fails. What should I do?
This is a duplicate state condition. Resolve the duplication first — destroy the mistakenly created target object, or remove the unwanted state entry with terraform state rm, then re-apply the moved block.
I need dozens of moved blocks to migrate count to for_each. What is an efficient approach?
Prepare a mapping table from old indexes to new keys and generate the moved blocks via a template or script. Apply the changes in small, incremental PRs and verify at each step that plan shows only moved entries.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...