Terraform state management is the most critical aspect of enterprise IaC. Poor state architecture leads to slow plans, state locking conflicts, blast radius issues, and team bottlenecks. This guide covers state backend selection, workspace strategies, state file organization patterns, and operational best practices from managing 500+ Terraform workspaces.
State Backend Selection
| Backend | Locking | Encryption | Best For |
|---|---|---|---|
| S3 + DynamoDB | ✅ | ✅ (KMS) | AWS-centric teams |
| Azure Blob | ✅ | ✅ (CMK) | Azure-centric teams |
| GCS | ✅ | ✅ | GCP-centric teams |
| Terraform Cloud | ✅ | ✅ | Multi-cloud, enterprise features |
AWS Backend Configuration
# backend.tf
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/networking/vpc/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "alias/terraform-state-key"
dynamodb_table = "terraform-locks"
}
}
Bootstrap the Backend
# Run once with local backend, then migrate
resource "aws_s3_bucket" "terraform_state" {
bucket = "mycompany-terraform-state"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform.arn
}
}
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
State File Organization
flowchart TB
subgraph Foundation ["Foundation Layer (Rarely Changes)"]
VPC["VPC / Networking"]
IAM["IAM Roles"]
DNS["Route 53 Zones"]
end
subgraph Platform ["Platform Layer"]
EKS["EKS Cluster"]
RDS["RDS Instances"]
Redis["ElastiCache"]
end
subgraph App ["Application Layer (Changes Often)"]
App1["App A"]
App2["App B"]
App3["App C"]
end
Foundation --> Platform --> App
style Foundation fill:#E1F5FE,stroke:#0277BD
style App fill:#FFF3E0,stroke:#E65100
Key principle: Separate by change frequency and blast radius. Foundation changes rarely; applications change often.
Repository Structure
terraform/
├── modules/ # Reusable modules
│ ├── vpc/
│ ├── eks/
│ └── rds/
├── environments/
│ ├── production/
│ │ ├── foundation/ # Separate state file
│ │ │ ├── main.tf
│ │ │ └── backend.tf
│ │ ├── platform/ # Separate state file
│ │ │ ├── main.tf
│ │ │ └── backend.tf
│ │ └── apps/
│ │ ├── app-a/ # Separate state file
│ │ └── app-b/
│ └── staging/
│ └── ...
└── global/ # Cross-environment resources
├── iam/
└── dns/
Cross-State References
# In platform/main.tf - reference foundation outputs
data "terraform_remote_state" "foundation" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "production/foundation/terraform.tfstate"
region = "us-east-1"
}
}
module "eks" {
source = "../../modules/eks"
vpc_id = data.terraform_remote_state.foundation.outputs.vpc_id
subnet_ids = data.terraform_remote_state.foundation.outputs.private_subnet_ids
}
Workspace Strategy
Use workspaces sparingly—they share code but create operational complexity:
- Good use: Short-lived feature environments
- Bad use: Production vs staging (use separate directories)
Key Takeaways
- Use remote backends with locking and encryption
- Separate state files by change frequency
- Use terraform_remote_state for cross-stack dependencies
- Avoid workspaces for environment separation
- Enable state file versioning for rollback
- Apply least-privilege IAM for state access
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.