Terraform State Locking with DynamoDB: Prevent Concurrent Modifications
Learn how to implement Terraform state locking with AWS DynamoDB to prevent concurrent modifications and state corruption. Complete setup guide with examples.
Cloud Computing
Master multi-account AWS management with Terraform. Learn provider aliases, cross-account IAM roles, AWS Organizations integration, and production-ready.
As organizations grow, a single AWS account quickly becomes a limitation. Security boundaries blur, billing gets complicated, and blast radius from mistakes affects everything. AWS recommends a multi-account strategy where different workloads, environments, and teams operate in isolated accounts.
Terraform is perfectly suited for managing this complexity. With provider aliases, assume role configurations, and modular design, you can orchestrate resources across dozens of AWS accounts from a single codebase. This article covers practical patterns for managing multi-account AWS environments with Terraform.
Before diving into the implementation, let's understand why organizations adopt multi-account strategies:
Terraform's provider alias feature lets you configure multiple AWS providers, each targeting a different account:
# providers.tf
provider "aws" {
region = "us-east-1"
alias = "management"
}
provider "aws" {
region = "us-east-1"
alias = "production"
assume_role {
role_arn = "arn:aws:iam::role/TerraformExecutionRole"
session_name = "terraform-production"
external_id = "terraform-prod-2025"
}
}
provider "aws" {
region = "us-east-1"
alias = "staging"
assume_role {
role_arn = "arn:aws:iam::role/TerraformExecutionRole"
session_name = "terraform-staging"
}
}
provider "aws" {
region = "us-east-1"
alias = "development"
assume_role {
role_arn = "arn:aws:iam::role/TerraformExecutionRole"
session_name = "terraform-development"
}
}Use the provider alias when creating resources:
resource "aws_vpc" "production" {
provider = aws.production
cidr_block = "10.1.0.0/16"
tags = {
Name = "production-vpc"
Environment = "production"
}
}
resource "aws_vpc" "staging" {
provider = aws.staging
cidr_block = "10.2.0.0/16"
tags = {
Name = "staging-vpc"
Environment = "staging"
}
}Each target account needs an IAM role that Terraform can assume. Create this role in every account:
# Module: cross-account-role
variable "management_account_id" {
description = "The AWS account ID of the management account"
type = string
}
resource "aws_iam_role" "terraform_execution" {
name = "TerraformExecutionRole"
max_session_duration = 3600
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.management_account_id}:root"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"sts:ExternalId" = "terraform-cross-account"
}
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "admin" {
role = aws_iam_role.terraform_execution.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}For production environments, replace AdministratorAccess with a custom policy that grants only the permissions Terraform actually needs.
For more flexible multi-account setups, use variables to configure providers dynamically:
# variables.tf
variable "accounts" {
description = "Map of account configurations"
type = map(object({
account_id = string
region = string
environment = string
}))
default = {
production = {
account_id = "111111111111"
region = "us-east-1"
environment = "production"
}
staging = {
account_id = "222222222222"
region = "us-east-1"
environment = "staging"
}
development = {
account_id = "333333333333"
region = "us-west-2"
environment = "development"
}
}
}If you're using AWS Organizations, Terraform can manage the organization structure itself:
resource "aws_organizations_organization" "main" {
aws_service_access_principals = [
"cloudtrail.amazonaws.com",
"config.amazonaws.com",
"sso.amazonaws.com",
]
feature_set = "ALL"
enabled_policy_types = [
"SERVICE_CONTROL_POLICY",
"TAG_POLICY",
]
}
resource "aws_organizations_organizational_unit" "workloads" {
name = "Workloads"
parent_id = aws_organizations_organization.main.roots[0].id
}
resource "aws_organizations_organizational_unit" "production" {
name = "Production"
parent_id = aws_organizations_organizational_unit.workloads.id
}
resource "aws_organizations_account" "prod_app" {
name = "prod-application"
email = "aws+prod-app@company.com"
parent_id = aws_organizations_organizational_unit.production.id
role_name = "TerraformExecutionRole"
lifecycle {
ignore_changes = [role_name]
}
}Enforce guardrails across all accounts using SCPs:
resource "aws_organizations_policy" "deny_regions" {
name = "deny-unauthorized-regions"
description = "Deny access to regions not approved for use"
type = "SERVICE_CONTROL_POLICY"
content = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyUnapprovedRegions"
Effect = "Deny"
Action = "*"
Resource = "*"
Condition = {
StringNotEquals = {
"aws:RequestedRegion" = ["us-east-1", "us-west-2", "eu-west-1"]
}
}
}
]
})
}
resource "aws_organizations_policy_attachment" "deny_regions" {
policy_id = aws_organizations_policy.deny_regions.id
target_id = aws_organizations_organizational_unit.workloads.id
}Some resources need to be shared across accounts. A common pattern is sharing VPCs using AWS RAM (Resource Access Manager):
# In the networking account
resource "aws_vpc" "shared" {
provider = aws.networking
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "shared_private" {
provider = aws.networking
vpc_id = aws_vpc.shared.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
}
resource "aws_ram_resource_share" "shared_subnets" {
provider = aws.networking
name = "shared-subnets"
allow_external_principals = false
}
resource "aws_ram_resource_association" "subnet" {
provider = aws.networking
resource_arn = aws_subnet.shared_private.arn
resource_share_arn = aws_ram_resource_share.shared_subnets.arn
}
resource "aws_ram_principal_association" "prod_account" {
provider = aws.networking
principal = var.accounts["production"].account_id
resource_share_arn = aws_ram_resource_share.shared_subnets.arn
}A recommended directory structure for multi-account Terraform:
├── modules/
│ ├── networking/
│ ├── security/
│ └── compute/
├── accounts/
│ ├── management/
│ │ ├── main.tf
│ │ ├── backend.tf
│ │ └── variables.tf
│ ├── production/
│ │ ├── main.tf
│ │ ├── backend.tf
│ │ └── variables.tf
│ └── staging/
│ ├── main.tf
│ ├── backend.tf
│ └── variables.tf
├── organization/
│ ├── main.tf
│ ├── accounts.tf
│ └── scps.tf
└── global/
├── iam.tf
└── dns.tfLearn by doing with interactive courses on CopyPasteLearn:
Related: AWS: Increase EC2 root_block_device size — resize your EC2 storage with Terraform.
Managing multiple AWS accounts with Terraform brings order to complex cloud environments. By leveraging provider aliases, cross-account IAM roles, and AWS Organizations, you can maintain consistent infrastructure across dozens of accounts while preserving the security and isolation benefits of multi-account architecture. Start with a clear account structure, implement proper IAM roles, and use modules to keep your configurations DRY and maintainable.
Learn how to implement Terraform state locking with AWS DynamoDB to prevent concurrent modifications and state corruption. Complete setup guide with examples.
Learn how to use Terraform data sources to query existing resources, look up AMIs, reference remote state, and build dynamic configurations. Complete.
Learn how to integrate Terraform with GitHub Actions for automated infrastructure deployments. Complete guide with workflows, best practices, and.
Protect your applications with AWS WAF rules managed by Terraform — rate limiting, IP blocking, and SQL injection prevention.