Study guide

11.3 Remote State and the backend Block

HashiCorp Certified: Terraform Associate 004

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Format, Delivery, Retakes, and Recertification 1.3 004 Changes, Terraform 1.12, and Study Plan

2 Infrastructure as Code with Terraform

2.1 What Infrastructure as Code Is 2.2 Advantages of IaC Patterns 2.3 Multi-Cloud, Hybrid, and Service-Agnostic Workflows

3 Terraform Fundamentals: Providers & State

3.1 Install and Version Terraform Providers 3.2 How Terraform Uses Providers 3.3 Configuration with Multiple Providers 3.4 How Terraform Uses and Manages State

4 Core Terraform Workflow: Init & Validate

4.1 The Terraform Write–Plan–Apply Workflow 4.2 Initialize a Working Directory 4.3 Validate a Configuration

5 Plan, Apply, Destroy, and Formatting

5.1 Generate and Review an Execution Plan 5.2 Apply Changes to Infrastructure 5.3 Destroy Terraform-Managed Infrastructure 5.4 Formatting and Style Adjustments

6 Resources, Data Sources, and References

6.1 Resource Blocks vs Data Blocks 6.2 Resource Attributes and Cross-Resource References 6.3 Resource Addressing and Interpolation Patterns

7 Variables, Outputs, and Complex Types

7.1 Input Variables, tfvars, and Precedence 7.2 Output Values 7.3 Complex Types: list, map, object, tuple, set

8 Dynamic Configuration and Dependencies

8.1 Expressions, count, for_each, and dynamic Blocks 8.2 Built-in Functions 8.3 Dependencies, depends_on, and Lifecycle Rules

9 Conditions, Secrets, and Sensitive Data

9.1 Custom Conditions: validation, precondition, postcondition, check 9.2 Sensitive Data Handling 9.3 Vault, Ephemeral Values, and Write-Only Arguments

10 Terraform Modules

10.1 How Terraform Sources Modules 10.2 Variable Scope within Modules 10.3 Using Modules in Configuration 10.4 Managing Module Versions

11 Terraform State Management

11.1 The Local Backend 11.2 State Locking 11.3 Remote State and the backend Block 11.4 Resource Drift and State Operations

12 Maintain Infrastructure with Terraform

12.1 Import Existing Infrastructure 12.2 Inspect State with the CLI 12.3 Verbose Logging and Diagnostics

13 HCP Terraform

13.1 Create Infrastructure with HCP Terraform 13.2 Collaboration and Governance Features 13.3 Workspaces and Projects 13.4 HCP Terraform Integrations

11.3 Remote State and the backend Block

Key Takeaways Explain This Topic AI

Configure remote state with one terraform { backend "TYPE" { ... } } block; you cannot combine it with a cloud block

Backend arguments cannot use variables, locals, or data source interpolations; use partial configuration and terraform init -backend-config instead

Terraform 1.12 ships built-in backends including s3, azurerm, gcs, http, consul, kubernetes, oss, plus local, remote, cos, oci, and pg

terraform init -migrate-state copies existing state to the new backend; -reconfigure ignores the old backend and does not copy

terraform_remote_state reads another state's root outputs; for HCP Terraform, HashiCorp recommends the tfe_outputs data source because it does not require full state access

Last updated: August 2026

11.3 Remote State and the backend Block

Quick Answer: Put one backend block inside terraform { } . Arguments are literal — no var , local , or data-source interpolations. Finish the rest with terraform init -backend-config . A cloud block (HCP Terraform) cannot sit next to a backend block. After a backend change, init -migrate-state copies state; init -reconfigure does not. terraform_remote_state reads another state's root outputs .

Objective 6c on Terraform Associate (004) is configuration, not philosophy. Official references: Backend block configuration , the terraform block ( backend vs cloud ), terraform init , and terraform_remote_state .

  • One backend block, literal arguments terraform {
  • required_version = ">= 1.12.0"
  • backend "s3" {
  • bucket = "tfstate-prod-004"
  • key = "network/terraform.tfstate"
  • region = "us-east-1"
  • use_lockfile = true
  • }
  • }

Official limitations:

A configuration can provide only one backend block.

A backend block cannot refer to named values (input variables, locals, or data source attributes).

You cannot reference values declared inside a backend block elsewhere in the configuration.

You cannot load additional backends as plugins . The type must ship in the Terraform 1.12 binary.

If the configuration includes a cloud block , it cannot include a backend block. HCP Terraform and Terraform Enterprise manage state in the workspace; you do not also point at S3 from the same root module.

The cloud block is the current HCP Terraform / Terraform Enterprise integration (the product formerly marketed as Terraform Cloud). It is mutually exclusive with backend . The older backend "remote" type still exists in 1.12 for compatibility; new HCP work is the cloud block. # Legal: HCP Terraform holds state. No backend block.

  • terraform {
  • cloud {
  • organization = "example_corp"
  • workspaces {
  • name = "network-prod"
  • }
  • }
  • }
  • Built-in backend types on Terraform 1.12

The v1.12.x docs directory publishes these backend types: local , remote , s3 , azurerm , gcs , http , consul , kubernetes , oss , cos , oci , and pg . 004 will not ask you to memorize every argument of every type. It will ask you to recognize that the type is the block label and that arguments are type-specific.

  • Type Where the snapshot lives (1.12 docs)
  • s3 Object at bucket / key (non-default workspaces under workspace_key_prefix , default env: )
  • azurerm Blob key in container_name on storage_account_name
  • gcs Object in a Google Cloud Storage bucket
  • http REST endpoint ( GET to read, configurable write method)
  • consul Consul KV
  • kubernetes Kubernetes secret
  • oss Alibaba Cloud Object Storage Service

HashiCorp recommends not putting access keys in the block. Leave credential arguments unset and use the environment variables or credential files that platform already uses ( AWS_PROFILE , ARM_USE_OIDC , and so on). If you hardcode secrets or pass them with -backend-config , Terraform copies them in plain text into .terraform/terraform.tfstate and into saved plan files.

Partial configuration and -backend-config

You may omit arguments and finish them at init time. That is a partial configuration . Terraform still needs an empty-or-partial backend "TYPE" block in a root .tf file so it knows the type. # backend.tf — committed. No secrets.

  • terraform {
  • backend "s3" {
  • key = "network/terraform.tfstate"
  • use_lockfile = true
  • }
  • } # backend.hcl or config.s3.tfbackend — not committed if it has secrets
  • bucket = "tfstate-prod-004"
  • region = "us-east-1" terraform init -backend-config=backend.hcl
  • terraform init -backend-config="bucket=tfstate-prod-004" -backend-config="region=us-east-1"

Ways to supply the rest, from the official backend page:

File — -backend-config=PATH . Recommended filename pattern: *.backendname.tfbackend (for example config.s3.tfbackend ). The file is the body of the backend block as top-level attributes, not wrapped in terraform { } .

Command-line key/value pairs — -backend-config="KEY=VALUE" . Shells keep history; do not put secrets here.

Interactive prompts — Terraform asks for missing required values unless input is disabled. It does not prompt for optional values.

Command-line options override the configuration file, and later -backend-config flags override earlier ones. The merged result is stored under .terraform/ and must not be committed.

Changing or removing a backend

You can change arguments or change types (local → s3, consul → s3). Terraform detects the change and requires reinitialization.

  • Init flag State data
  • migrate-state Copy existing state to the new backend; may prompt per workspace
  • force-copy Same copy, answers yes; implies -migrate-state
  • reconfigure Disregard existing backend configuration; do not migrate
  • backend=false Skip backend configuration (already-initialized dir; useful so validate can run)

Back up first ( cp terraform.tfstate … or terraform state pull > backup.tfstate ). If you have multiple CLI workspaces, Terraform can copy all of them and will ask. If you are only fixing an argument on the same backend, it still asks; you can answer no when there is nothing to copy.

To go back to local: delete the backend block and re-init. Terraform prompts to migrate the remote snapshot onto the default local backend.

terraform_remote_state versus HCP workspace data

terraform_remote_state is a built-in data source ( terraform.io/builtin/terraform ). It reads the latest snapshot from a specified backend and exports root module outputs only . Nested module outputs are invisible unless the other root re-exports them. It does not create resources in the other configuration. data "terraform_remote_state" "network" {

  • backend = "s3"
  • config = {
  • bucket = "tfstate-prod-004"
  • key = "network/terraform.tfstate"
  • region = "us-east-1"
  • }
  • }
  • resource "aws_instance" "app" {
  • subnet_id = data.terraform_remote_state.network.outputs.public_subnet_id
  • }

The config object uses the same arguments the backend block would use. Nested backend blocks become objects ( workspaces = { name = "vpc-prod" } , not a workspaces { } block).

Security warning from HashiCorp: although only outputs are exposed in HCL, anyone who can read those outputs can usually read the entire state snapshot over the network. Do not use terraform_remote_state as a secrets channel.

For HCP Terraform , HashiCorp recommends the tfe_outputs data source in the hashicorp/tfe provider instead. It is more secure because it does not require full workspace-state access to fetch outputs. terraform_remote_state still works with backend = "remote" and an organization/workspace config; that is the older pattern, not the preferred HCP pattern on 004.

Even better when you can: publish the shared value somewhere purpose-built (SSM Parameter Store, DNS, a Consul KV key) and read it with a normal data source so consumers never need state permissions.

Scenario: network stack and app stack

The network root uses backend "s3" with key = "network/terraform.tfstate" and use_lockfile = true . It outputs public_subnet_id . The app root has its own backend ( key = "app/terraform.tfstate" ) and a terraform_remote_state data source pointing at the network key. Each stack has one writer and one lock. The app cannot var.bucket its way into the backend block; bucket names that differ per account come from -backend-config at init. When the team later moves the app stack to HCP Terraform, they remove the app backend block, add a cloud block, and run terraform init -migrate-state . They do not leave both blocks in the file.

004 traps for objective 6c

Backend arguments are not interpolable. Partial config is the escape hatch.

One backend block. No cloud + backend .

-migrate-state copies; -reconfigure does not.

terraform_remote_state is outputs-only and still implies full-state read access.

Prefer tfe_outputs when the other workspace lives in HCP Terraform.

Credentials in the block or in -backend-config land in .terraform/ and in plan files.