Back to Blog

Terraform Backend Configuration Internals for the Terraform Associate (004) Exam: The backend Block Rules, Partial Configuration & -backend-config, What terraform init Stores in .terraform, -migrate-state vs -reconfigure vs -backend=false, S3 Native Locking with use_lockfile, How Every Backend Locks, Workspace Layouts, Credentials Precedence, terraform_remote_state and the cloud Block

A practitioner's guide to Objective 6 of the HashiCorp Terraform Associate (004) exam, 'Terraform state management': the exact rules of the backend block (one per configuration, no variables, no cloud block alongside it), partial configuration through -backend-config files and key=value pairs, the backend state Terraform writes under .terraform and how init detects a changed backend, when to use -migrate-state, -reconfigure, -force-copy and -backend=false, the S3 backend's use_lockfile native locking and the deprecated DynamoDB table, how local, azurerm, gcs, http, pg, kubernetes, consul and oci backends implement locking, workspace key layouts per backend, credential sourcing and why secrets must not live in the block, -lock, -lock-timeout and force-unlock, terraform_remote_state's security trade-off, and the cloud block versus the remote backend.

By Sailor Team , November 1, 2026

Objective 6 of the Terraform Associate (004) exam is called Terraform state management, and three of its four sub-objectives are about backends: describe the local backend, describe state locking, and configure remote state using the backend block. Our state management guide covers the why: what state contains, why a remote backend with locking matters, and the terraform state subcommands. It gives the S3 backend example and two one-line rules (no variables in the block, re-run init after a change).

This article is about the mechanics behind those two lines. What exactly is Terraform storing when you run init against a backend, and how does it know the backend changed? Which flag copies state, which one refuses to, and which one skips the backend entirely? What does “native S3 locking” mean now that the DynamoDB table is deprecated? Where does each backend put a non-default workspace, and which backends can lock at all? The exam asks these as scenario questions (“the team wants to move state from local to S3 without losing it”, “init fails with a backend configuration changed error in CI”, “a developer’s apply died and the next run says the state is locked”), and the answers all live in the backend block, the .terraform directory and a handful of init flags.

A version note before we start: the 004 exam page states that it tests on Terraform 1.12. The current stable release at the time of writing is 1.17.1, and the backend behaviour below is the same across that range except where a version is called out.

What a Backend Is (and Is Not)

A backend is the component that decides where state is stored and how it is locked. In Terraform’s current architecture that is all it does; execution of plan and apply always happens on the machine running the CLI, except for the remote backend and the cloud block, which hand operations to HCP Terraform or Terraform Enterprise.

terraform {
  required_version = ">= 1.12.0"

  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "networking/prod/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
    encrypt      = true
  }
}

The documented rules for that block are short and every one of them is a question:

RuleExact behaviour
One backend per configuration”A configuration can only provide one backend block.” A second one is a configuration error
Only in the root moduleBackend blocks in child modules are ignored; the root module decides where state lives
No named values”A backend block cannot refer to named values (like input variables, locals, or data source attributes).” The error is Variables not allowed
Not referenceableNothing in the configuration can read the backend’s arguments
Mutually exclusive with cloud”If your configuration includes a cloud block, it cannot include a backend block.”
Default is localOmit the block and Terraform stores terraform.tfstate next to the configuration
Built in, not pluggable”You cannot load additional backends as plugins.” The set is fixed per CLI version

The “no variables” rule exists because the backend must be resolved before Terraform can evaluate anything: variables, locals and data sources all require a state and a provider, and both come after backend initialization. The sanctioned escape hatch is partial configuration, covered next.

The current built-in list is local, remote, azurerm, consul, cos, gcs, http, kubernetes, oci, oss, pg and s3. Terraform 1.3 removed artifactory, etcd, etcdv3, manta and swift; a configuration naming one of those fails init on any supported release. If a question lists “Artifactory backend” as an option, it is a distractor on current Terraform.

Partial Configuration: Keeping Secrets and Environments Out of the Block

Because the block cannot read variables, Terraform lets you leave arguments out and supply them at init time. The docs call this partial configuration, and there are three sources, which can be combined:

  1. A file passed with -backend-config=PATH. The file holds top-level key = value attributes (not a backend block), and the recommended naming is *.backendname.tfbackend, for example prod.s3.tfbackend.
  2. Key/value pairs passed as -backend-config="KEY=VALUE", repeatable. The docs warn these land in shell history, so they are for non-secret values such as bucket names.
  3. Interactive prompts for any required argument still missing, unless input is disabled with -input=false, in which case init errors. Terraform “will not prompt for optional values”.

The minimal block is just the type:

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

Two consequences follow, and both are exam fodder.

The merged configuration is written to disk. “The final, merged configuration is stored on disk in the .terraform directory.” Anything you pass through -backend-config, including an access key or a Postgres connection string, is stored in plain text there and in any saved plan file. The documented recommendation for credentials is therefore environment variables, not -backend-config, and .terraform must never be committed.

Partial configuration is how one configuration serves many environments. The same main.tf with an empty backend "s3" {} block is initialized against dev.s3.tfbackend in one pipeline and prod.s3.tfbackend in another. That is also the recommended alternative to CLI workspaces for strongly isolated environments, as our workspaces guide explains.

What terraform init Stores, and How It Detects a Changed Backend

After a successful init, the working directory contains .terraform/terraform.tfstate. Despite the name, that file is not your infrastructure state; it is the backend state: the backend type, the merged configuration, and a hash of it. Terraform also records which workspace is selected in .terraform/environment. The whole directory is relocatable with TF_DATA_DIR, which is how CI systems keep it out of the repository checkout.

On every later init, Terraform compares the backend block (plus any -backend-config input) with the stored configuration. If they differ, the docs say Terraform “will automatically detect any changes in your configuration and request a reinitialization”, and since Terraform 0.15 that request is an error rather than an automatic migration:

Error: Backend configuration changed

A change in the backend configuration has been detected, which may require
migrating existing state.

If you wish to attempt automatic migration of the state, use "terraform init -migrate-state".
If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".

The CLI reference states the rule in one line: “Either -reconfigure or -migrate-state must be supplied to update the backend configuration.” Which one you pick is the single most tested backend decision.

The four init flags that control backends

FlagWhat it doesWhen it is the answer
-migrate-state”Attempt to copy existing state to the new backend.” Prompts to confirm, and with multiple workspaces asks whether to copy all of themMoving state from local to S3, from S3 to GCS, between buckets or keys, or into HCP Terraform
-reconfigure”Disregards any existing configuration, preventing migration of any existing state.” The new backend is adopted as if the directory were freshThe state already exists at the new location (another engineer migrated it) or you intentionally want to start pointing at a different, already-populated state
-force-copy”Suppresses these prompts and answers yes to the migration questions.” Implies -migrate-stateNon-interactive pipelines that must migrate
-backend=false”To skip backend configuration.” Providers and modules still installterraform validate or fmt jobs in CI with no backend credentials; the docs advise using it only in a directory already initialized for that backend

Two traps hide in that table. -reconfigure does not delete or move anything; it simply stops Terraform from looking at the old backend, so if the new location is empty your next plan proposes creating everything again. And -migrate-state copies state between backends, not between workspaces; the destination keeps its own workspace layout, which is why Terraform asks about workspaces during migration when the old backend had several.

Removing a backend

Deleting the backend block and running init is a backend change like any other. Terraform prompts to migrate the state to the local backend; with -migrate-state you end up with terraform.tfstate on disk. That is the documented way to “go back to local”, and it is also why an accidental deletion of the block in a pull request is dangerous: a reviewer who approves it and runs init -migrate-state moves production state onto a laptop.

Backing up before a migration

The docs are blunt: “Before migrating to a new backend, we strongly recommend manually backing up your state.” The practical command is terraform state pull > backup.tfstate against the old backend before changing the block. Migration itself is a read-from-old, write-to-new copy; it does not delete the source, which is why old S3 keys linger after a move.

The Local Backend in Detail

Objective 6a is “describe the local backend”, so know its defaults rather than just its existence.

SettingDefaultNotes
pathterraform.tfstate relative to the root moduleOverridable in the block
workspace_dirterraform.tfstate.dNon-default workspaces live at terraform.tfstate.d/<name>/terraform.tfstate
Backupterraform.tfstate.backup written before each state change-backup=PATH overrides, -backup=- disables
LockingYes, “using system APIs” (an OS-level file lock)Protects against two processes on the same machine, nothing more

Two facts matter in scenarios. The local backend does lock, so “state locking is only available with remote backends” is false. And an explicit backend "local" { path = "../shared/terraform.tfstate" } is legal; it is the one backend that terraform_remote_state can read without any remote service, which the data source docs demonstrate with a path under config.

State Locking: What Locks, What Doesn’t, and How to Recover

The language docs define the contract: “If supported by your backend, Terraform will lock your state for all operations that could write state.” Locking is automatic, silent on success, and fatal on failure: “If acquiring the lock is taking longer than expected, Terraform will output a status message”, and if it cannot acquire the lock at all it stops.

ControlBehaviour
-lock=falseDisables locking for that command. The docs say “we do not recommend it”
-lock-timeout=<duration>How long to wait for a held lock. Default 0s, so by default Terraform fails immediately rather than queueing; -lock-timeout=5m is the usual CI setting
terraform force-unlock LOCK_IDRemoves a stuck lock. “The force-unlock command requires a unique lock ID”, printed in the error when the lock is held. Use only for a lock you hold whose automatic release failed; unlocking someone else’s active run “could cause multiple writers”

The lock ID is a nonce: it ensures you unlock the exact lock the error reported, not a newer one acquired in the meantime. There is no force-unlock for backends whose locks release automatically with the session, which is why the pg backend documents that force-unlock is not supported.

How each backend locks

The exam asks “which backends support locking” more often than it should. Current answer: all of the built-in backends can lock, with one conditional.

BackendLocking mechanismNotes
localOS file lockSame-machine protection only
s3Native lock file <key>.tflock in the bucket (use_lockfile = true), or the deprecated DynamoDB tableDetail below
azurermAzure Blob Storage native capabilities (a lease on the state blob)Also “consistency checking”
gcsSupported nativelyDocs just say “This backend supports state locking”
consulConsul sessions; lock = true by defaultCan be disabled
pgPostgres advisory locksReleased with the session, so no force-unlock
kubernetesA Lease resource next to the state SecretSecret named tfstate-<workspace>-<secret_suffix>
ociIf-None-Match: * conditional write creating a .lock objectAdded in recent releases
cos, ossNative (Tencent COS) / Alibaba Tablestore table (oss)Rare on the exam
httpOnly if lock_address is set; defaults to disabled. Uses LOCK / UNLOCK HTTP methods, expects 423 or 409 when heldThe one backend that can silently run without locking
remote / cloudHCP Terraform workspace lockAlso serialises runs

The http backend is the trap. A configuration with only address set has no locking at all; the docs mark lock_address and unlock_address as “Defaults to disabled”. A scenario about “state corrupted by concurrent applies against a custom HTTP state service” is answered by adding lock_address and unlock_address, not by -lock=true.

The S3 Backend: Native Locking and the DynamoDB Deprecation

The S3 backend is the one the exam names most, and it changed meaningfully in 2025.

use_lockfile replaces the DynamoDB table

Terraform 1.10 introduced S3-native locking as an experiment, and Terraform 1.11 (27 February 2025) made it generally available: “S3 native state locking is now generally available” and “we’ve deprecated the DynamoDB-related arguments in favor of this new locking mechanism”. The current docs describe dynamodb_table as “deprecated and will be removed in a future minor version”, and the DynamoDB endpoint settings likewise.

use_lockfile = truedynamodb_table (deprecated)
Lock object<key>.tflock in the same bucket, e.g. networking/prod/terraform.tfstate.tflockAn item in a DynamoDB table with a LockID partition key of type String
Extra infrastructureNoneA table per state store
IAMs3:GetObject, s3:PutObject, s3:DeleteObject on the .tflock objectdynamodb:DescribeTable, GetItem, PutItem, DeleteItem
MigrationBoth can be set at once “to support migration”; run with both, then drop the tableStill works with a deprecation warning on 1.12 and 1.13
Defaultfalse, so a block with neither setting does not lockNot set

That last row is the exam trap: an S3 backend with only bucket, key and region has no locking. Since the 004 exam is pinned to Terraform 1.12, the “correct” modern answer to “add locking to an S3 backend” is use_lockfile = true, while “create a DynamoDB table with a LockID key” is the legacy answer that still appears in older material. If a question offers both, the native lock file is the one HashiCorp now recommends.

Everything else in the block

  • Required: bucket, key, region (region can also come from AWS_REGION or AWS_DEFAULT_REGION).
  • Encryption: encrypt = true turns on server-side encryption for the state and lock objects; kms_key_id selects a KMS key and needs kms:Encrypt, kms:Decrypt and kms:GenerateDataKey; sse_customer_key supplies an SSE-C key, which should come from AWS_SSE_CUSTOMER_KEY because a value in the block “persists it to terraform.tfstate” (the backend state file).
  • Minimum IAM: s3:ListBucket on the bucket plus s3:GetObject and s3:PutObject on the key. s3:DeleteObject on the state key is not needed because “Terraform does not delete it”; delete is needed only for the lock object and for non-default workspace keys.
  • Versioning: the docs call enabling bucket versioning “highly recommended” so you can recover from accidental deletions. Versioning is your undo; the backend has none of its own.
  • Workspaces: the default workspace lives at key; every other workspace lives at <workspace_key_prefix>/<workspace>/<key>, and workspace_key_prefix defaults to env:, so env:/development/networking/prod/terraform.tfstate.
  • Account guard rails: allowed_account_ids and forbidden_account_ids (mutually exclusive) make init fail if the credentials resolve to the wrong account, which is the cheap answer to “prevent a dev pipeline from touching prod state”.
  • Credentials: access_key/secret_key (must be set together), token, profile, shared_config_files and shared_credentials_files, the assume_role block (role_arn required, duration 15 minutes to 12 hours, external_id, session_name, source_identity, policy, policy_arns, tags; multiple blocks are assumed in order), and assume_role_with_web_identity for OIDC with web_identity_token or web_identity_token_file. The documented guidance is to prefer environment variables and the shared config files over anything in the block.
  • S3-compatible stores: endpoints { s3 = ... }, use_path_style, skip_credentials_validation, skip_region_validation, skip_requesting_account_id, skip_metadata_api_check and skip_s3_checksum exist for MinIO-style targets, and HashiCorp supports them “best effort” only.

A full production block today looks like this:

terraform {
  backend "s3" {
    bucket               = "acme-terraform-state"
    key                  = "networking/prod/terraform.tfstate"
    region               = "eu-west-1"
    use_lockfile         = true
    encrypt              = true
    kms_key_id           = "arn:aws:kms:eu-west-1:123456789012:key/abcd-1234"
    allowed_account_ids  = ["123456789012"]
    assume_role {
      role_arn = "arn:aws:iam::123456789012:role/terraform-state"
    }
  }
}

The Other Backends at Exam Depth

You do not need every argument, but each backend has one or two facts that distinguish it in a question.

BackendRequiredWorkspace layoutDistinguishing facts
azurermstorage_account_name, container_name, keyBlob named <key>env:<workspace>Entra ID auth recommended: OIDC/workload identity (use_oidc, ARM_OIDC_TOKEN), managed identity (use_msi), Azure CLI (use_cli); use_azuread_auth switches data-plane access to RBAC (Storage Blob Data Contributor) instead of the account key; snapshot = true snapshots the blob before each write; access keys and SAS tokens are “not recommended for new workloads”; resource_group_name only needed with lookup_blob_endpoint
gcsbucket (must already exist)<prefix>/<workspace>.tfstate, default workspace as default.tfstateencryption_key (customer-supplied, 32-byte base64) vs kms_encryption_key (Cloud KMS); credentials / GOOGLE_BACKEND_CREDENTIALS / GOOGLE_CREDENTIALS with the warning that the provider also reads GOOGLE_CREDENTIALS; impersonate_service_account needs the Token Creator role; object versioning “highly recommended”
httpaddressCaller-definedupdate_method defaults to POST; lock_address/unlock_address off by default; retry_max 2, retry_wait_min 1 s, retry_wait_max 30 s; basic auth via TF_HTTP_USERNAME/TF_HTTP_PASSWORD; mTLS via PEM arguments; skip_cert_verification false by default
pgconn_str (or PG_CONN_STR / libpq env vars)Rows keyed by workspace name in table statesSchema terraform_remote_state auto-created unless skip_schema_creation; advisory locks, no force-unlock
kubernetessecret_suffixSecret tfstate-<workspace>-<secret_suffix>, chunked with -<index> when largeLease-based locking; auth via config_path, in_cluster_config or host/token; needs Secret read/write in the namespace
consulpath<path>-env:<workspace>lock defaults true; gzip option; CONSUL_HTTP_TOKEN
ocibucket, namespace (and region for most setups)Prefixed keys with .lock objectsAuth via API key, instance/resource principal, security token or OKE workload identity; KMS and SSE-C encryption
remoteorganization, workspaces { name } or workspaces { prefix }HCP Terraform workspacesSuperseded by the cloud block; prefix becomes tags on migration

The gcs credential warning is a nice exam detail: setting GOOGLE_CREDENTIALS for the backend silently configures the Google provider too, so a backend-only service account can end up managing resources.

Workspaces and Backends

CLI workspaces are “named instances of state inside a single backend”. The exam facts are the layouts above plus one principle: switching workspaces never touches the backend configuration, only which key or object Terraform reads. Non-default workspaces need extra permissions on some backends; the S3 docs add s3:DeleteObject on <workspace_key_prefix>/*/<key> because terraform workspace delete removes the object. If the backend does not support workspaces at all, terraform workspace new fails with a backend error.

During init -migrate-state, Terraform notices if the source backend has several workspaces and asks whether to copy all of them. When the destination backend is single-workspace, it asks you to pick one. The multi-workspace question is where most “lost staging state during migration” stories come from.

terraform_remote_state: Reading Another Backend

The terraform_remote_state data source takes a backend, an optional workspace, a config object mirroring that backend’s block, and defaults for outputs that may be missing. It exposes only root-module outputs through outputs. The security trade-off is explicit in the docs: the reader “must have access to the entire state snapshot, which often includes some sensitive information”, and the docs recommend alternatives such as tfe_outputs for HCP Terraform or publishing values to SSM Parameter Store or Consul so that access to the shared values can differ from access to the state. Our multiple states guide compares those three options in practice.

data "terraform_remote_state" "network" {
  backend   = "s3"
  workspace = "prod"
  config = {
    bucket       = "acme-terraform-state"
    key          = "networking/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.network.outputs.private_subnet_id
}

Note that the data source reads state and never locks it; it does not conflict with a run in the producing configuration.

The cloud Block vs the remote Backend

HCP Terraform is configured with a cloud block, which is not a backend in the backend "x" sense even though it occupies the same role. Our HCP Terraform guide covers runs, policy and the private registry; the backend-level facts are:

  • cloud { organization, hostname (default app.terraform.io), workspaces { name | tags, project } }; exactly one of name or tags. tags now accepts a map of key/value strings as well as the legacy key-only list. The same values can come from TF_CLOUD_ORGANIZATION, TF_CLOUD_HOSTNAME, TF_CLOUD_PROJECT and TF_WORKSPACE.
  • Credentials come from terraform login (stored in the CLI credentials file), a credentials "app.terraform.io" { token = ... } block in the CLI configuration, or TF_TOKEN_app_terraform_io. The token attribute inside the block is possible but discouraged for the same plain-text reason as other backends.
  • Replacing backend "remote" with a cloud block keeps the same workspaces; a prefix becomes tags, and afterwards you address workspaces by full name.
  • Migrating from any other backend is the normal init migration prompt, with the twist that Terraform may ask you to rename workspaces to globally unique names such as networking-prod-eu-west-1.
  • With cloud or remote, plan and apply execute remotely by default; every other backend only stores state.

Credentials: Where They Should Come From

Three documented statements settle every “where do I put the secret” question:

  1. “We recommend using environment variables to supply credentials and other sensitive data.”
  2. Values in the block or in -backend-config “are written in plain text to the .terraform subdirectory and to plan files”.
  3. “Do not check this directory into Git.”

So the ranking is: workload identity or instance role (no secret at all) > environment variables and shared credential files > -backend-config file kept out of version control > hard-coded values, which is never the answer. Backend credentials are also separate from provider credentials: the S3 backend’s assume_role and the AWS provider’s assume_role are configured independently, which is how a pipeline can read state in a central “state account” while deploying into a workload account.

Scenario Drill

ScenarioAnswerWhy
Move existing local state to a new S3 bucket without recreating resourcesAdd the backend "s3" block, terraform state pull as a backup, terraform init -migrate-state, confirm the copyMigration copies state; -reconfigure would leave it behind
Another engineer already moved state to the new bucket; your init errors with “Backend configuration changed”terraform init -reconfigureThe destination is already populated; do not re-copy
CI must migrate without promptsterraform init -force-copyAnswers yes to migration questions
A validate-only CI job has no cloud credentials and init fails on the backendterraform init -backend=false then terraform validateSkips backend configuration, still installs providers
One root module must target dev and prod state storesEmpty backend "s3" {} + -backend-config=dev.s3.tfbackend / prod.s3.tfbackendPartial configuration
backend "s3" { bucket = var.bucket } failsBackend blocks cannot reference variables; use partial configuration”Variables not allowed”
S3 backend with bucket, key, region only; two applies corrupted stateAdd use_lockfile = trueNo locking by default; DynamoDB is deprecated
Existing DynamoDB-locked backend must move to native locking safelySet both use_lockfile = true and dynamodb_table, run normally, then remove dynamodb_tableDocumented dual-lock migration
apply was killed; next run reports the state is locked with an IDterraform force-unlock <ID> only after confirming no run is activeLock ID is required
Runs in a busy pipeline fail instantly with lock errors-lock-timeout=5mDefault is 0s
Custom HTTP state service shows concurrent writesConfigure lock_address and unlock_addressHTTP locking is off unless set
Accidentally applied prod config with dev credentialsallowed_account_ids / forbidden_account_ids in the S3 backendinit fails on the wrong account
State must be encrypted with a specific KMS keyencrypt = true + kms_key_id, grant Encrypt/Decrypt/GenerateDataKeySSE-KMS
Recover a state file someone deleted from the bucketRestore the previous object versionVersioning is the undo
Where is the staging workspace state in S3?env:/staging/<key>workspace_key_prefix default env:
Where is it with the local backend?terraform.tfstate.d/staging/terraform.tfstateworkspace_dir default
Secrets appear in .terraform/terraform.tfstateExpected for anything passed via -backend-config; move secrets to env varsBackend state is plain text
Child module declares a backendIgnored; only the root module’s backend countsRoot-module rule
Config has both cloud {} and backend "s3" {}Invalid; remove oneMutually exclusive
Migrating from backend "remote" with prefix to the cloud blockReplace the block, turn prefix into tags, address workspaces by full name afterwardsDocumented migration
Team wants read-only access to another configuration’s outputs without exposing the whole statePublish to SSM/Consul or use tfe_outputs, not terraform_remote_stateRemote state needs full snapshot access
Setting GOOGLE_CREDENTIALS for the GCS backend changed provider behaviourUse GOOGLE_BACKEND_CREDENTIALS for the backendProvider reads GOOGLE_CREDENTIALS too
Azure backend should stop using the storage account keyuse_azuread_auth = true with OIDC or managed identity and Storage Blob Data ContributorEntra ID auth is the recommended path
Configuration references backend "swift"Removed in Terraform 1.3; migrate to a supported backendRemoved backends

Common Mistakes

  • Running -reconfigure when you meant -migrate-state. Nothing is lost, but the new backend is empty and the next plan wants to create everything. Your old state is still at the old location.
  • Assuming an S3 backend locks out of the box. It locks only with use_lockfile = true or the deprecated DynamoDB table.
  • Putting access keys in the block or in -backend-config “just for CI”. They are written in plain text to .terraform and plan files.
  • Forgetting the backup. terraform state pull > backup.tfstate before every migration is the documented expectation.
  • Believing the local backend has no locking. It locks with OS file locks.
  • Using force-unlock on someone else’s lock. The ID requirement exists to make you think; multiple writers corrupt state.
  • Treating terraform_remote_state as a scoped read. It needs full read access to the snapshot, sensitive values included.
  • Expecting a child module’s backend block to do something. Only the root module’s block is honored.
  • Mixing cloud and backend. The configuration will not initialize.

Frequently Asked Questions

What is the difference between -migrate-state and -reconfigure?

-migrate-state copies the state from the previously configured backend to the new one, prompting first. -reconfigure makes Terraform forget the previous backend configuration and adopt the new one without touching any state. Use migrate when you are moving state; use reconfigure when the state already exists at the new location or you are intentionally switching to a different, populated state store.

Does the S3 backend still need a DynamoDB table?

No. Since Terraform 1.11, use_lockfile = true locks with an object in the same bucket, and the DynamoDB arguments are deprecated and scheduled for removal. Both can be set simultaneously while you migrate. The 004 exam tests on Terraform 1.12, where native locking is the current recommendation.

Why can’t a backend block use variables?

Terraform must know where state lives before it can evaluate variables, locals or data sources, because evaluation itself depends on state and providers. Partial configuration through -backend-config is the supported way to inject environment-specific values.

What is in .terraform/terraform.tfstate?

The backend state: the backend type and the merged configuration (including anything passed with -backend-config), plus a hash Terraform uses to detect configuration changes on the next init. It is not your resource state, but it can contain credentials, so it must not be committed.

Which backends support state locking?

All current built-in backends can lock, with the http backend locking only when lock_address is configured, and the S3 backend locking only when use_lockfile or the deprecated dynamodb_table is set. The pg backend uses advisory locks and does not support force-unlock.

How does -lock-timeout differ from -lock=false?

-lock-timeout keeps locking enabled but waits up to the given duration for a held lock instead of failing immediately (the default is 0s). -lock=false disables locking entirely for that command, which the docs recommend against.

Is the cloud block a backend?

Functionally it fills the same role (where state lives, who locks it) and is mutually exclusive with a backend block, but it also moves plan and apply execution to HCP Terraform and is configured with its own block and environment variables rather than through backend "remote".

Conclusion

Objective 6 rarely asks “what is a backend”. It asks whether you know that the block cannot hold a variable, that partial configuration is the sanctioned workaround and that its merged result sits in plain text under .terraform; that a changed backend stops init until you choose between copying state and ignoring it; that an S3 block without use_lockfile does not lock and the DynamoDB table is on its way out; that the local backend locks too; and that the http backend will happily run unlocked. Layer on the per-backend workspace layouts, the force-unlock ID rule, the terraform_remote_state trade-off and the cloud block’s exclusivity, and most state-management questions collapse to one or two eliminations.

The fastest way to make those eliminations automatic is to run the commands. Initialize a project locally, migrate it to S3 with -migrate-state, break the backend on purpose and fix it with -reconfigure, kill an apply and recover with force-unlock, then pressure-test the reflexes against realistic distractors. Sailor.sh’s HashiCorp Terraform Associate Mock Exam Bundle includes eight 60-question exams whose explanations spell out why the -reconfigure answer, the DynamoDB answer or the “variables in the backend block” answer loses to the one the scenario actually demands. Pair this guide with the Terraform Associate exam guide, the 30-day study plan and the provider installation internals guide for the other half of what init does, then test yourself on the Terraform Associate practice questions before booking the real thing.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now