By the end you'll know
- What state is, and why Terraform needs it
- How to look inside it with
terraform state listandterraform state show - Why state can hold secrets, and must never go in Git
- What happens when state is lost
- How to rename a resource safely with a
movedblock, and stop tracking one withterraform state rm
Where you are
At the end of Lesson 2, you could read every kind of change in a plan. But each of those plans depended on something you haven't looked at yet: Terraform knew hello.txt already existed, knew its id, and knew what it contained before you changed it. That knowledge lives in one file, terraform.tfstate, and this lesson is about that file.
Before you start, set your terraform-practice folder back to a known point. Make sure main.tf holds just the one resource from Lesson 1, with its original content:
resource "local_file" "hello" {
filename = "hello.txt"
content = "Hello from Terraform!"
}
Run terraform apply, and type yes if it plans a change.
What state is
When you leave your coat in a cloakroom, you get a ticket, and the attendant keeps the other half. The ticket is how they know which coat on the rail is yours. Without it, they'd be guessing, and a coat that looks like yours isn't necessarily yours.
State is Terraform's half of the ticket. For every resource in your configuration, it records which real thing in the world belongs to it, by its id, along with everything Terraform knows about it. That's how terraform plan works: it reads your files for what you want, reads the state for what it made, checks the real world, and works out the difference.
Your main.tf says "there should be a file called hello.txt". Only the state says "and this one, with this id, is the one I made".
Looking inside
You can ask Terraform what it's tracking:
terraform state list
local_file.hello
One resource, by its address: the type and the name together. In a big project, this list is the quickest way to see everything Terraform manages. To see everything it knows about one resource:
terraform state show local_file.hello
Your screen shows a few more lines, mostly checksums of the file's content. These are the ones that matter:
# local_file.hello:
resource "local_file" "hello" {
content = "Hello from Terraform!"
filename = "hello.txt"
id = "cadbafd9572a72dc7769c97730552f5955e135fd"
}
Now open terraform.tfstate itself in your editor. It's JSON, the format from Python Lesson 5, and you'll find the same details in it, including "content": "Hello from Terraform!". Look, but don't edit it by hand: Terraform has commands for every change you'd want to make, and a typo here can make Terraform lose track of real things. You'll also see a terraform.tfstate.backup file next to it: the state as it was before the last change, kept in case something goes wrong.
State holds secrets
Look again at that content line. Terraform recorded exactly what it wrote into the file. If that content had been a password, the password would be sitting in terraform.tfstate, in plain text.
That's true of everything Terraform creates. A database's admin password, an API token, a private key: if Terraform made it, or was told it, the value is in state. Lesson 5 shows how to mark values as sensitive, which hides them from your screen, but it doesn't hide them from the state file. So:
- Never commit state to Git. Anyone who can read the repository, today or in five years, would have every secret in it.
- Treat the state file like a password. On a shared project, it lives somewhere encrypted and access-controlled, which is Lesson 8.
If your project is a Git repository, a .gitignore file with these lines keeps state, and Terraform's downloaded providers, out of it:
.terraform/
*.tfstate
*.tfstate.*
In my real setup
CivicGrid's state contains the API's database connection string, password included. Terraform creates the Kubernetes Secret that holds it, and everything Terraform creates is recorded in state, secrets and all. That's why the state never lives on my laptop: it's stored encrypted in HCP Terraform, which Lesson 8 covers. And the repository's .gitignore blocks *.tfstate anyway, with a comment that says "NEVER commit these".
Renaming safely with moved
In Lesson 2, renaming hello to greeting made Terraform plan to destroy one resource and create another, because to Terraform a new name is a new resource. With a file that's harmless, but you don't want to destroy a database just to give it a better name in your code.
A moved block tells Terraform "this is the same resource, under a new name". Change main.tf to this:
resource "local_file" "greeting" {
filename = "hello.txt"
content = "Hello from Terraform!"
}
moved {
from = local_file.hello
to = local_file.greeting
}
The resource has its new name, and the moved block says where it came from. Run terraform plan:
# local_file.hello has moved to local_file.greeting
resource "local_file" "greeting" {
id = "cadbafd9572a72dc7769c97730552f5955e135fd"
# (2 unchanged attributes hidden)
}
Plan: 0 to add, 0 to change, 0 to destroy.
Your screen may say a different number of unchanged attributes. What matters is the first line, "has moved to", and the summary: nothing added, nothing destroyed. Run terraform apply and type yes: Terraform updates the name in its state, and the file isn't touched. Run terraform state list to see the new address, local_file.greeting.
Once a move has been applied everywhere the project is used, the moved block can be deleted. Until then, leave it in: it does no harm, and it's also a note to anyone reading the code about what changed.
Break it on purpose
Try each one, then put it back.
Lose the state. Move the state file somewhere else, as if it had been deleted, then plan:
mv terraform.tfstate ../saved.tfstate
terraform plan
mv moves a file, here into the folder above. The plan says:
# local_file.greeting will be created
and Plan: 1 to add, 0 to change, 0 to destroy. But hello.txt is right there. Without state, Terraform has no ticket: it can't know that file is its own, so it plans to make a new one. With a file, applying would just write over it. With a cloud server, applying would create a second server, while the first one keeps running and billing you, and Terraform no longer knows it exists. Don't apply. Put the state back, and plan again:
mv ../saved.tfstate terraform.tfstate
terraform plan
No changes. Your infrastructure matches the configuration.
Stop tracking something. Sometimes you want Terraform to let go of a resource without destroying it, for example because another project is taking it over. Run:
terraform state rm local_file.greeting
Removed local_file.greeting
Successfully removed 1 resource instance(s).
terraform state list now shows nothing, but hello.txt is still on disk: Terraform has torn up its ticket, and that's all. Run terraform plan, and, just like losing the state, it plans to create the file again, because the code still describes one. In real life, after state rm you'd delete or move the resource's block too. Here, run terraform apply and type yes: Terraform writes the file again and starts tracking it, with nothing lost. (Bringing something that already exists under Terraform, without recreating it, is called importing, and it's a topic for after this track.)
Edit state by hand. Don't. This one's a warning rather than an exercise: hand-editing terraform.tfstate is how people make Terraform lose track of real infrastructure, or think things exist that don't. Every change you'd want has a command, like moved and state rm above.
Where you are now
You know what state is, how to read it, why it holds secrets, and what happens without it. You can rename a resource without destroying it, and stop tracking one without deleting it. So far, everything has been a file on your own computer. Lesson 4 adds a second provider, Docker, and real infrastructure that depends on other infrastructure.
Check yourself
What is Terraform's state?
What does terraform state list show?
Why must terraform.tfstate never go in Git?
You rename a resource and add a moved block. What does the plan show?
You lose terraform.tfstate, but hello.txt still exists. What does terraform plan show?
What does terraform state rm local_file.greeting do?
Key terms
- State: Terraform's record of what it created, kept in
terraform.tfstateby default. - Address: a resource's type and name together, like
local_file.hello. - terraform state list: lists every resource in the state.
- terraform state show: shows everything the state records about one resource.
- moved block: tells Terraform a resource has a new name, so it isn't destroyed and recreated.
- terraform state rm: removes a resource from the state, without touching the real thing.
- Importing: bringing something that already exists under Terraform's management.
- .gitignore: the file that tells Git which files to leave out, like
*.tfstate.
Go deeper
- State (opens in a new tab) in the Terraform documentation explains what it stores and why.
- Refactoring with moved blocks (opens in a new tab) covers renames and more.