Terraform
Advanced 16 min read Terraform

Remote Backends & Collaboration

A backend decides where state is stored and how locking works. Move from the local backend to S3 + DynamoDB or Terraform Cloud so teams share one safe source of truth.

backendremote statestate lockings3dynamodbterraform cloudpartial configuration
Prerequisites: State Management
Introduction

What is this?

A backend defines where Terraform stores its state and how locking is handled. The default local backend writes terraform.tfstate in the working directory. Remote backends — S3, Azure Blob, GCS, etcd or Terraform Cloud — store state remotely and add locking so multiple people can safely run plan/apply on the same infrastructure.

Motivation

Why do you need it?

  • Shared state lets a team work on the same infrastructure from any machine.
  • Locking prevents concurrent writes that would corrupt the state file.
  • Remote state survives a lost laptop and is encrypted at rest.
  • CI/CD can apply from any runner because state is not tied to a local file.
  • Backend upgrades like s3 to Terraform Cloud are non-destructive migrations.
Think about it

Mental model

Imagine

A team's shared git repository

Think of

A remote backend

Everyone pulls from and pushes to the same repository instead of emailing files. Terraform teams pull state, acquire a lock, apply, and push back — the lock is the merge protection that prevents clobbering.

Mechanics

How it works

  • terraform init reads the backend block and configures where state lives.
  • On every plan/apply, Terraform downloads the latest state and acquires a lock.
  • After apply, the updated state is uploaded and the lock is released.
  • The S3 backend pairs with a DynamoDB table for locking; Azure uses blob leases.
  • Partial configuration keeps credentials and bucket names out of version control via -backend-config.

Remote backend with locking.

terraform plan/apply
Lock (DynamoDB)
State in S3 bucket
Write + unlock
Next run reads it
Reference

Key commands

terraform init

Initialize the backend and download providers.

terraform init -reconfigure

Re-initialize the backend after changing its configuration.

terraform init -backend-config=backend.tfvars

Initialize with partial configuration values from a file.

terraform force-unlock <LOCK_ID>

Release a stale state lock (only after verifying no run is active).

terraform state pull

Download the current remote state to stdout.

terraform state push

Upload a local state file to the remote backend.

terraform providers lock

Write the dependency lock file for reproducible provider versions.

Hands-on

Examples

An S3 backend with locking and partial config

An S3 backend with locking and partial config
terraform {
backend "s3" {}
}
# backend.tfvars (kept out of git, or via -backend-config)
bucket = "my-org-terraform-state"
key = "environments/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
# init with partial configuration
# terraform init -backend-config=backend.tfvars

The backend block stays empty in code. Sensitive or environment-specific values are supplied at init time with -backend-config, so the same repo works for dev and prod while nothing secret lands in git.

Real world

In production

  • S3 + DynamoDB is the standard AWS remote backend: durable, cheap, versioned and lockable.
  • Terraform Cloud/Enterprise manages state, locking and run history for you.
  • Separate state per environment and per stack so failures stay contained.
  • Cross-workspace reads via terraform_remote_state share VPC and network outputs.
Guidelines

Best practices

  • Always use a remote backend once more than one person touches the infrastructure.
  • Enable bucket versioning so a bad apply can be rolled back from state history.
  • Encrypt state with SSE-KMS and restrict bucket/DynamoDB access with IAM.
  • Keep bucket names globally unique and enable server-side encryption.
  • Store backend configuration secrets in env vars or a git-ignored file, never in the block.
Watch out

Common mistakes

  • Forgetting dynamodb_table, which silently disables locking.
  • Committing a backend block with credentials or account IDs inline.
  • Not enabling versioning on the state bucket, losing recovery ability.
  • Running apply without re-initializing after switching backends.
Root cause analysis

Common issues

Error
Error: Backend configuration changed
Root cause

The backend block was edited after the last init.

Fix

Re-run terraform init -reconfigure to adopt the new backend settings.

Error
Error acquiring the state lock
Root cause

A lock is held by an active run or left behind by a crashed one.

Fix

Check for an active run, then terraform force-unlock <LOCK_ID> if stale.

Error
AccessDenied: Access Denied while reading/writing state
Root cause

The IAM role or keys lack permissions on the bucket or DynamoDB table.

Fix

Grant s3:GetObject/PutObject/DeleteObject on the state object and full access to the lock table.

When things break

Debugging tips

  • terraform state pull shows exactly what the remote backend holds.
  • Check the S3 bucket object and its versions to compare against expectations.
  • terraform init -reconfigure reloads backend settings after edits.
  • TF_LOG=DEBUG reveals the exact S3/DynamoDB API calls and permissions errors.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/3
1

Which pairing provides remote state plus locking on AWS?

2

What does -backend-config do?

3

The local backend supports state locking.

Quick reference

Cheat sheet

Backend operations
terraform initInit backend
terraform init -reconfigureReload backend config
terraform init -backend-config=f.tfvarsPartial config
terraform force-unlock IDClear stale lock
terraform state pullRead remote state
terraform state pushWrite remote state
Go deeper

Official documentation