Interactive lesson

Terraform state and remote backends

Lesson player Chapter 1

Terraform state

Terraform's memory.

Where to keep it, and how to protect it.

Words you'll meet

StateTerraform's record of what it manages.
BackendWhere that record is kept.
LockStops two people changing it at once.
DriftWhen the real world no longer matches your code.

What state is

Your code
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags       = { env = "prod" }
}
State file Terraform's address book
aws_vpc.mainisvpc-0a1b2c3d
Refreshed: checked with AWS just now
Empty. Terraform has forgotten this network.
Real world: AWS
VPCvpc-0a1b2c3d10.0.0.0/16Terraform is asking
VPCnew IDa second network
 terraform plan (trimmed)
!y # aws_vpc.main will be updated in-place
!g   + tags = { "env" = "prod" }
!p Plan: 0 to add, 1 to change, 0 to destroy.
 terraform plan with no state (trimmed)
!r # aws_vpc.main will be created
!r Plan: 1 to add, 0 to change, 0 to destroy.

What's inside state

{ "resources": [{
    "type": "aws_db_instance",
    "name": "main",
    "instances": [{ "attributes": {
        "id": "shop-db",
        "username": "admin",
        "password": "Sup3rS3cret!"
    }}]
}]}
A trimmed-down state file
On your screen
!g + password = (sensitive value)
Hidden
In the state file
!r "password": "Sup3rS3cret!"
Still there, in plain text
Terraformsends the secret straight toAWSnever written to state
OpenTofu 1.7+: can encrypt the whole state fileNever commit state to Git

Why local state breaks

Terraform's default is local state: a file on your own machine.

One laptop
.tfstate
Laptop dies: Terraform forgets
Two people
v7
v5
Two versions of the truth
Pushed to Git
Secrets stay in the history

The fix: a shared remote backend, set up properly.

Remote state in S3

Backend block in your root module
terraform {
  backend "s3" {
    bucket       = "shop-tf-state"
    key          = "prod/network/terraform.tfstate"
    region       = "eu-west-2"
    encrypt      = true
    use_lockfile = true
  }
}
One shared copy used by Amara and Ben
S3 bucketprod/network/terraform.tfstate
encryptedversions: v1 v2 v3
In Terraform, no variables in this block. The bucket must already exist.
 terminal
!p $ terraform init -migrate-state
!m Initializing the backend...
!y Do you want to copy existing state to the new backend?
!m   Enter a value: yes
!g Successfully configured the backend "s3"!

Locking

Amara
terraform apply
finished
State
in S3
.tflock: held by Amara
lock released
Ben
terraform apply
stopped
up to date
 Ben's terminal
!r Error: Error acquiring the state lock
!m Lock Info:
!m   Who:       amara@laptop
!m   Operation: OperationTypeApply
Amara's apply is done, so the lock is gone. Ben plans again, from her finished work.
backend "s3" {
  # bucket, key, region as before
  use_lockfile = true
}
Stuck lock after a crash? terraform force-unlock, only when nobody else is running.

Choosing state boundaries

everything.tfstate

dev and prod · network, app and database · all in one file

s3://shop-tf-state
dev/network/terraform.tfstatea bad apply stays here
prod/network/terraform.tfstatefewer people can read
prod/app/terraform.tfstatefewer people can read
net-anet-bsg-1sg-2app-1app-2db-1dnsiamlogscachequeue
Too many tiny states: you spend your days wiring them together.

When reality drifts

Your code security group, trimmed
ingress {
  from_port = 443
  to_port   = 443
}
ingress {
  from_port   = 22
  to_port     = 22
  cidr_blocks = ["203.0.113.10/32"]
}
State file
aws_security_group.app
Remembers: port 443
Refresh-only plan: 1 change made outside Terraform
Code, state and AWS agree again
Real world: AWS
Rule443from code
Rule22added by hand
Rule22removed by apply
Rule22now in code
 terminal
!p $ terraform plan -refresh-only
!y Note: Objects have changed outside of Terraform
!y   # aws_security_group.app has been changed
Terraform sees drift only when a plan runs. HCP Terraform can check daily.

Working with state safely

State file Terraform's address book
Open it in a text editor? No.
!p $ terraform state list
import {
  to = aws_s3_bucket.logs
  id = "shop-logs"
}
!p $ terraform state rm aws_s3_bucket.old
v9 damagedv8v7: roll back here
aws_vpc.main→vpc-0a1b2c3d
aws_s3_bucket.old
aws_s3_bucket.oldforgotten
aws_s3_bucket.logsimported
Real world: AWS
VPCvpc-0a1b2c3d
Bucketshop-oldstill here
Bucketshop-logsbuilt by hand
Bucketshop-logsmanaged now

Recap

Your code
what you want
State the address book
access controlencryptionversioninglocking
Real world: AWS
what exists
  1. State links your code to real things, and can hold secrets.
  2. Keep it in a protected shared backend: access control, encryption, versioning, locking.
  3. Choose state boundaries on purpose. When reality drifts, make code the truth.
  4. Change state only with Terraform's own tools.

Press play for narration, animated diagrams and quick checks.

0:00 / 0:00

Lesson map

Chapters

    How our diagrams speak: Teal: focus or current concept Green: desired or successful Brick red: risk or conflict Slate blue: a relationship or link
    Build it for real: look inside stateoptional · about 10 min

    You'll look inside a real state file, spring the "sensitive" trap, make Terraform forget something, and watch a lock stop a second run. This carries on from episode 1's lab folder, so do that one first.

    You need: Terraform 1.4 or newer (install guide) or OpenTofu (install guide), and a terminal. No cloud account. No cost. Everything stays on your computer.

    1. What does state know about?

    terraform state list

    Eight addresses, such as module.development.terraform_data.subnet[0] and module.prod.terraform_data.network. That's the address book.

    2. Open one entry

    terraform state show 'module.prod.terraform_data.subnet[0]'

    You'll see its id and its value, 10.20.0.0/24.

    3. The sensitive trap

    Add this to main.tf, then run terraform apply:

    # LAB ONLY: never put a real password in code.
    variable "db_password" {
      type      = string
      sensitive = true
      default   = "Sup3rS3cret!"
    }
    
    resource "terraform_data" "db" {
      input = {
        password = var.db_password
      }
    }
    

    The plan shows password = (sensitive value). Hidden on screen.

    Now search the state file for it. macOS or Linux:

    grep -c "Sup3rS3cret" terraform.tfstate

    Windows PowerShell:

    Select-String -Path terraform.tfstate -Pattern "Sup3rS3cret"

    It's there, in plain text. (In our OpenTofu 1.12.6 test it appeared twice.) This is why state must be protected. Look at the file if you like, but never edit it.

    4. Make Terraform forget something

    terraform state rm 'module.prod.terraform_data.subnet[2]'
    terraform plan

    Successfully removed 1 resource instance(s). Then Plan: 1 to add: Terraform forgot it, so it plans to create it again. With a real cloud resource, that create could make a second object, or fail because the name or ID is already taken. Run terraform apply to tidy up.

    5. Watch a lock work (two terminals, same folder)

    Terminal 1: start a change, and don't answer the question yet.

    terraform apply -var "db_password=Changed123"

    Terminal 2: try to plan at the same time.

    terraform plan

    Error: Error acquiring the state lock, with Lock Info showing Operation: OperationTypeApply. While terminal 1 waits, a file called .terraform.tfstate.lock.info exists. Now type no in terminal 1: the lock is released.

    6. Your local safety net

    Next to terraform.tfstate you'll find terraform.tfstate.backup: the previous version. S3 bucket versioning is the grown-up version of this.

    7. Clean up

    terraform destroy

    Type yes. Everything was local, so nothing else is affected.

    Tested step by step with OpenTofu 1.12.6 on 30 September 2026. The lab uses only features built into both tools. With OpenTofu, type tofu instead of terraform, and messages say "OpenTofu" instead of "Terraform". It could not be run with the Terraform program itself in the test environment.

    How to use this lesson

    Glossary. Tap any dotted-underlined term for a plain-English explanation. Playback pauses while you read.

    Keyboard. Space or K plays/pauses, left and right arrows change chapter, and 1–4 answer a quick check.

    Narration. The lesson currently uses your device's speech engine. The content is already segmented so recorded or cloned voice clips can replace it later without redesigning the lesson.