Terraform State Management · Topic 2 of 2

Remote Backends and Resource Drift

Terraform Associate 004: configure a remote backend, partial configuration, migrate state, detect drift with refresh-only, use moved and removed blocks.

  • 5 min
In a nutshell
  • The backend block lives inside the terraform block, one per configuration, with no variables, locals or data source references.
  • Partial configuration supplies missing values with terraform init -backend-config.
  • Run terraform init after any backend change; -migrate-state copies state, -reconfigure ignores it.

What it is

A remote backend stores state in a shared service such as an S3 bucket, Azure Storage or Google Cloud Storage instead of on one laptop. You configure it with a backend block inside the terraform block.

Resource drift is the difference between what state records and what really exists, usually because someone changed a resource outside Terraform. Managing state also covers refactoring: renaming resources or handing them off without destroying them.

Add backend blockterraform initMigrate stateShared statelocked and versionedDetect driftrefresh only plan

How it works

The backend block

terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "network/terraform.tfstate"
    region       = "us-east-1"
    use_lockfile = true
  }
}

For the S3 backend, bucket, key and region are required, and locking is opt in with use_lockfile = true, which writes a .tflock file beside the state. The older dynamodb_table locking is deprecated.

Backend ruleDetail
One per configurationA configuration can have only one backend block
No named valuesThe block cannot refer to input variables, locals or data source attributes
Not with cloudA configuration uses either a backend block or a cloud block, never both
ReinitializeAfter any change to the backend, run terraform init again

Partial configuration

Because variables are not allowed, you leave some arguments out and supply them at init time. The block still names the backend type.

terraform {
  backend "s3" {}
}
terraform init -backend-config=prod.s3.tfbackend
terraform init -backend-config="key=prod/terraform.tfstate"

Values can come from a file, from KEY=VALUE pairs or from interactive prompts. Terraform writes the final backend settings in plain text to .terraform/terraform.tfstate and into saved plan files, so supply credentials through environment variables instead.

Changing or migrating a backend

When you add, change or remove a backend block, terraform init detects it and offers to copy existing state to the new location. terraform init -migrate-state asks for that migration explicitly, while terraform init -reconfigure uses the new settings and ignores the existing state. Removing the backend block and running init moves you back to the local backend.

Reading another configuration's state

The terraform_remote_state data source reads the root module outputs of another state, so one configuration can use values another one publishes.

data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "acme-terraform-state"
    key    = "network/terraform.tfstate"
    region = "us-east-1"
  }
}

You then refer to data.terraform_remote_state.network.outputs.subnet_id. Only outputs are available, not the other configuration's resources.

Detecting and handling drift

terraform plan and terraform apply refresh state against real infrastructure by default, so drift shows up as proposed changes. To see drift on its own, use refresh only mode.

CommandEffect
terraform plan -refresh-onlyShows how state would change to match real infrastructure; changes nothing
terraform apply -refresh-onlyUpdates state to match real infrastructure, without changing any resource
terraform plan -refresh=falseSkips the refresh and plans from state as recorded

These replace the deprecated terraform refresh command, which updated state with no chance to review. After accepting drift into state, either update the configuration to match or run a normal apply to put the resource back as the configuration says.

Refactoring state with moved and removed blocks

moved {
  from = aws_instance.web
  to   = aws_instance.app
}

removed {
  from = aws_instance.legacy
  lifecycle {
    destroy = false
  }
}

A moved block tells Terraform a resource has a new address, such as a rename or a move into a module, so it updates state instead of destroying and recreating it. A removed block with destroy = false takes a resource out of state while leaving the real object in place. The CLI equivalents are terraform state mv and terraform state rm; the blocks are preferred because they go through plan and review.

How the exam asks about this

  • "The backend block must use var.environment for the key." Not allowed; use partial configuration with -backend-config at init.
  • "Someone resized an instance in the console; you want state to match without changing anything." Run terraform apply -refresh-only.
  • "You renamed a resource in code and plan shows destroy and create." Add a moved block from the old address to the new one.

What to remember for the exam

  • The backend block lives inside the terraform block, one per configuration, with no variables, locals or data source references.
  • Partial configuration supplies missing values with terraform init -backend-config.
  • Run terraform init after any backend change; -migrate-state copies state, -reconfigure ignores it.
  • terraform_remote_state reads only the root module outputs of another state.
  • -refresh-only on plan or apply replaces the deprecated terraform refresh.
  • moved renames without recreation; removed with destroy = false stops managing without destroying.
Watch out

terraform apply -refresh-only changes state, not infrastructure. It accepts drift; it does not fix it. To undo a manual change, run a normal plan and apply from the configuration.

Common mistakes

Putting secrets in the backend block. Backend settings are saved in plain text under .terraform and in plan files. Use environment variables or the backend's own credential chain.

Forgetting to reinitialize. Editing the backend block does nothing until terraform init runs. Plan and apply report that the backend needs initializing.

Deleting a resource block to stop managing it. Deleting the block makes Terraform plan a destroy. Use a removed block with destroy = false or terraform state rm to keep the real object.

Check yourself

  • Can a backend block reference an input variable?

    No. Backend blocks cannot use variables, locals or data source attributes, so supply changing values through partial configuration with -backend-config.

  • What does terraform plan -refresh-only do?

    It shows how state would change to match real infrastructure after changes made outside Terraform, without changing state or resources.

  • How do you rename a resource without destroying it?

    Add a moved block from the old address to the new one, or use terraform state mv, so Terraform updates state instead of recreating the object.

Topic 2 of 2 in Terraform State Management

Test yourself on Terraform State Management

The full HashiCorp Terraform Associate 004 question bank has 600 questions, with timed practice exams and an explanation for every answer.

This study guide is independent and is not affiliated with, endorsed by, or sponsored by HashiCorp. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.