Skip to main content

Terraform Backend

A Terraform backend defines where Terraform stores its state file and how operations like plan and apply are executed. The default backend stores state locally on disk — production teams use remote backends like S3 or Terraform Cloud to share state safely across a team.

What Is a Terraform Backend?

The backend answers two questions: where does the state file live, and where does Terraform run its operations? By default, state lives on your laptop in terraform.tfstate. The moment a second engineer joins your team, that stops working — you need a remote backend that everyone can access, with locking to prevent concurrent applies.

Think of the backend as the shared filing cabinet for your team's infrastructure records. The local backend is your personal desk drawer — fine when you work alone, unusable for a team.

Local Backend (Default)

HCL
# No backend block = local backend
# State stored at ./terraform.tfstate
# Fine for learning, bad for teams

S3 Remote Backend (Standard for AWS Teams)

HCL
# versions.tf
terraform {
backend "s3" {
bucket = "razorpay-terraform-state" # S3 bucket for state
key = "prod/api-service/terraform.tfstate" # path within bucket
region = "ap-south-1"
encrypt = true # SSE encryption for state
dynamodb_table = "terraform-state-lock" # DynamoDB table for locking
}
}

Setting Up the S3 Backend Prerequisites

Bash
# Create S3 bucket with versioning
aws s3api create-bucket \
--bucket razorpay-terraform-state \
--region ap-south-1 \
--create-bucket-configuration LocationConstraint=ap-south-1
# Enable versioning so you can recover old state
aws s3api put-bucket-versioning \
--bucket razorpay-terraform-state \
--versioning-configuration Status=Enabled
# Create DynamoDB table for state locking
aws dynamodb create-table \
--table-name terraform-state-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region ap-south-1

Initialising a Backend

Bash
# After adding or changing the backend block:
terraform init
# Terraform will ask: "Do you want to copy existing state to the new backend?"
# Answer yes to migrate local state to S3
# If you change backend config without changing the backend type:
terraform init -reconfigure
# Migrate state from one backend to another:
terraform init -migrate-state

Partial Backend Configuration

Hardcoding bucket names in the backend block makes it hard to reuse across environments. Use partial config:

HCL
# versions.tf — only the backend type, no values
terraform {
backend "s3" {}
}
Bash
# Pass values at init time — different per environment
terraform init \
-backend-config="bucket=razorpay-terraform-state" \
-backend-config="key=prod/payments/terraform.tfstate" \
-backend-config="region=ap-south-1" \
-backend-config="dynamodb_table=terraform-state-lock"

Terraform Cloud Backend

HCL
terraform {
cloud {
organization = "razorpay"
workspaces {
name = "payments-prod"
}
}
}

Backend Comparison

Backend State Storage Locking Best For
local Local disk None Solo learning only
s3 AWS S3 DynamoDB AWS teams
gcs GCP Cloud Storage Built-in GCP teams
azurerm Azure Blob Built-in Azure teams
Terraform Cloud Managed Built-in Any team, managed
Security

Restrict who has access to the S3 state bucket. The state file contains plaintext sensitive values. Use a bucket policy that allows only the CI/CD role and senior engineers to read the bucket directly.

Common Mistake

Putting the backend S3 bucket and DynamoDB table inside the same Terraform configuration they are used for. You cannot use Terraform to create the bucket that stores its own state. Create these resources manually or with a separate bootstrap configuration.

Frequently Asked Questions

Why is local state considered dangerous for anything beyond solo experimentation?

Local backend state lives as a plaintext JSON file on one engineer's disk with no locking, no encryption at rest by default, and often containing sensitive values (like database passwords) in plain text — if that laptop is lost, or two people run apply from different local copies, state gets lost or corrupted with no coordination mechanism. Remote backends like S3 (with DynamoDB locking) or Terraform Cloud solve locking, versioning, and shared access in one move.

What's a common mistake teams make when migrating from local to remote state?

Not backing up the local `terraform.tfstate` file before running `terraform init -migrate-state`, or having two engineers migrate to different remote backends simultaneously, causing a split-brain where Terraform's record diverges from reality. Also worth knowing: switching backends requires re-running `terraform init`, and state files often contain secrets in plaintext, so the remote backend needs its own access controls and encryption, not just locking.