Terraform State Strategy
Decision: Store Terraform state in each workload account, not centralized in the management account.
Why Per-Account State?
Security Isolation
| Aspect | Centralized (Bad) | Distributed (Good) |
|---|---|---|
| Dev compromise | Exposes prod state | Only dev affected |
| IAM roles | Complex cross-account | Simple per-account |
| Audit trail | Mixed in one bucket | Clear per account |
| Compliance | Hard to demonstrate | Easy separation |
Industry Best Practices
AWS Well-Architected Framework:
"Store Terraform state in the same account as the resources being managed. This maintains the security boundary and follows the principle of least privilege."
Gruntwork/Terragrunt:
"Each environment (dev/staging/prod) should have its own state backend to prevent accidental cross-environment modifications and maintain security isolation."
Compliance Requirements
HIPAA:
- Production PHI-related infrastructure state should NOT be in same bucket as dev/test
- Separate accounts = separate audit trails
- Easier to demonstrate to auditors
SOC 2:
- Logical separation between environments
- Different access controls per environment
- Clear audit trails per environment
State Storage Architecture
Dev Account (216482851263)
├── S3: docustack-terraform-state-216482851263
│ ├── vpc/terraform.tfstate
│ ├── ecs/terraform.tfstate
│ └── rds/terraform.tfstate
└── DynamoDB: docustack-terraform-locks
Staging Account (staging-id)
├── S3: docustack-terraform-state-<staging-id>
└── ...
Production Account (prod-id)
├── S3: docustack-terraform-state-<prod-id>
└── ...
Management Account (348665872333)
├── S3: docustack-terraform-state-348665872333
│ └── management/scp-policies/terraform.tfstate # Only mgmt resources
└── DynamoDB: docustack-terraform-locks
Configuration
Account Configuration
# infrastructure-live/dev/account.hcl
locals {
account_name = "dev"
account_id = "216482851263" # Dev account
}
# infrastructure-live/staging/account.hcl
locals {
account_name = "staging"
account_id = "<staging-account-id>"
}
# infrastructure-live/prod/account.hcl
locals {
account_name = "prod"
account_id = "<prod-account-id>"
}
# infrastructure-live/management/account.hcl
locals {
account_name = "management"
account_id = "348665872333"
}
Root Terragrunt Config
# infrastructure-live/root.hcl
remote_state {
backend = "s3"
config = {
# State bucket in SAME account as resources
bucket = "docustack-terraform-state-${local.account_id}"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "docustack-terraform-locks"
}
}
Deployment Workflow
Deploying to Dev
# Authenticate to DEV account
export AWS_PROFILE=docustack-sandbox-pwrusr
aws sts get-caller-identity # Verify: 216482851263
# Deploy bootstrap (creates state bucket)
cd infrastructure-live/bootstrap
terragrunt apply
# Creates: docustack-terraform-state-216482851263
# Deploy infrastructure
cd ../dev/us-east-1/vpc
terragrunt apply
# State stored in: s3://docustack-terraform-state-216482851263/dev/us-east-1/vpc/
Deploying to Management
# Switch to management account
export AWS_PROFILE=docustack-management-admin
aws sts get-caller-identity # Verify: 348665872333
# Deploy bootstrap for management
cd infrastructure-live/bootstrap-management
terragrunt apply
# Creates: docustack-terraform-state-348665872333
# Deploy management resources
cd ../management/us-east-1/scp-policies
terragrunt apply
# State stored in: s3://docustack-terraform-state-348665872333/management/us-east-1/scp-policies/
Common Mistakes
Wrong: Centralized State
# DON'T DO THIS
locals {
account_id = "348665872333" # Management account for ALL state
}
Problems:
- Security risk: Developers need cross-account access to management
- Blast radius: Compromise of management = all environments exposed
- IAM complexity: Complex cross-account role assumptions
- Against AWS best practices: Management should be minimal
- HIPAA concern: Dev environment state in same bucket as prod
Right: Per-Account State
# DO THIS
locals {
account_id = "216482851263" # Dev account for dev state
}
Benefits:
- Security isolation: Dev state can't access prod state
- Compliance ready: Separate state for HIPAA/SOC2
- Least privilege: Developers only access dev account state
- Blast radius reduction: Compromise limited to one account
FAQ
Q: Can accounts share one bootstrap bucket?
A: No. Security isolation requires separate buckets.
Q: What if we have 50 accounts?
A: Use automation script. Still only ~5 min per account, $0.50/month each.
Q: Can we skip bootstrap and use Terragrunt auto-init?
A: Yes, but manual bootstrap gives more control and is easier to troubleshoot.
Q: What happens if we delete the bootstrap bucket?
A: You lose all state! Only delete after destroying all resources in that account.
Q: Do all accounts need the same DynamoDB table name?
A: Yes, for consistency. Table is per-account, so no conflicts.