Objective
By the end of this lab, you will have:
- Created a resource with a name that is going to be renamed.
- Renamed the resource in the configuration.
- Added a
movedblock to preserve the state. - Verified the plan is empty after the refactor.
- Verified the real-world file was not recreated.
Requirements
- A Linux or macOS workstation with shell access.
- The Terraform CLI 1.9.x or later installed.
Scenario
You have a configuration that creates a file with a name that
is no longer appropriate. The refactor renames the resource
without recreating the file. The moved block is the
production control.
Tasks
Task 1: Create the working directory
mkdir -p ~/rb-moved-lab
cd ~/rb-moved-lab
Task 2: Initial configuration
Create main.tf:
terraform {
required_version = ">= 1.9.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform!\n"
}
Task 3: Initialise and apply
terraform init
terraform apply
Verify the file:
cat ~/rb-moved-lab/hello.txt
Task 4: Inspect the state
terraform state list
terraform state show local_file.greeting
The state has the local_file.greeting resource.
Task 5: Refactor the resource name
Edit main.tf to rename the resource:
resource "local_file" "app_greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform!\n"
}
Task 6: Plan without a moved block
terraform plan
The plan shows:
# local_file.greeting will be destroyed
+ resource "local_file" "app_greeting" {
# ...
}
Plan: 1 to add, 0 to change, 1 to destroy.
The plan proposes to destroy the old resource and create the new one. Do not apply. This is the production antipattern.
Task 7: Add the moved block
Edit main.tf to add the moved block:
moved {
from = local_file.greeting
to = local_file.app_greeting
}
resource "local_file" "app_greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform!\n"
}
Task 8: Plan with the moved block
terraform plan
The plan is now:
No changes. Your infrastructure matches the configuration.
The state has been updated to the new address. The real-world file is unchanged.
Task 9: Apply the refactor
terraform apply
The apply is a no-op. The state is updated; the real world is unchanged.
Task 10: Verify the state
terraform state list
Expected:
local_file.app_greeting
The state has the new address.
cat ~/rb-moved-lab/hello.txt
The file content is unchanged.
Task 11: Verify the file was not recreated
ls -la ~/rb-moved-lab/hello.txt
The files modification time is unchanged. The file was not recreated.
Task 12: Remove the moved block
The moved block is one-shot. After the refactor, it can be removed:
# Removed the moved block
resource "local_file" "app_greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform!\n"
}
terraform plan
The plan is still empty.
Validation
The lab is successful if:
- The resource was renamed in the configuration.
- The moved block was added.
- The plan is empty after the refactor.
- The real-world file was not recreated.
- The state has the new resource address.
Expected Outcome
At the end of the lab:
+---------------------------------+
| ~/rb-moved-lab/ |
| .terraform/ |
| .terraform.lock.hcl |
| hello.txt |
| main.tf |
+---------------------------------+
The hello.txt file content is unchanged. The state has the
new resource address. The plan is empty.
Cleanup
cd ~/rb-moved-lab
terraform destroy
rm -rf .terraform .terraform.lock.hcl terraform.tfstate*
The main.tf is the only artefact worth keeping.
What You Learned
You learned the moved block pattern:
- The refactor is in two parts. The configuration is renamed, and the moved block tells Terraform the new address.
- The state is updated, not the real world. The moved block is a state operation.
- The plan is empty after the refactor. The refactor is invisible to the apply.
- The moved block is one-shot. It can be removed after the refactor.
- A wrong moved block is binding. The next plan would show the wrong binding.