HOW I STRUCTURE MULTI-ACCOUNT AWS ORGANIZATIONS
How I Structure Multi-Account AWS Organizations
Single-account AWS setups stop working the moment you need compliance boundaries, independent billing, or blast radius isolation. Every serious engagement I’ve worked on has required multi-account architecture from day one.
Why Multi-Account
Three reasons come up every time:
- Blast radius — A misconfigured IAM policy in a dev account shouldn’t be able to touch production data. Account boundaries are the hardest security perimeter AWS offers.
- Compliance isolation — When a pharma client needs GDPR and SOC2 compliance, you don’t want audit scope bleeding across workloads that don’t need it.
- Billing clarity — Cost allocation tags are unreliable. Separate accounts give you clean cost attribution per team, project, or environment.
Real Engagements
On a pharma project I managed 40+ AWS accounts across a Digital-SDLC organization. SOC2 compliance was mandatory. Every account needed consistent guardrails, logging, and networking — no exceptions.
On a public sector engagement it was 10+ accounts, but the compliance requirements were even stricter: FSBP, CIS Benchmarks, and NIST 800-53 controls applied across the board.
Both projects followed the same core pattern.
Account Taxonomy
I use specialized accounts instead of dumping everything into one “shared services” account:
- Management — AWS Organizations root, billing, and Control Tower
- Networking — Transit Gateway, VPN, DNS, and centralized egress
- Logging — CloudTrail, Config, VPC Flow Logs aggregation
- Artifacts — ECR, S3 buckets for build artifacts, shared AMIs
- Backup — AWS Backup with cross-account vaulting for DR
- Audit — Security Hub, GuardDuty delegated admin, compliance dashboards
Workload accounts (dev, staging, prod) sit underneath, provisioned through AFT.
AFT: Account Factory for Terraform
Control Tower gives you the baseline. AFT automates the provisioning. When a team needs a new account, they submit a pull request:
module "new_workload_account" {
source = "./modules/aft-account-request"
control_tower_parameters = {
AccountEmail = "aws+project-alpha-prod@company.com"
AccountName = "project-alpha-prod"
ManagedOrganizationalUnit = "Workloads/Production"
SSOUserEmail = "admin@company.com"
SSOUserFirstName = "Admin"
SSOUserLastName = "User"
}
custom_fields = {
environment = "prod"
project = "alpha"
compliance = "soc2,gdpr"
cost_center = "engineering"
}
account_customizations_name = "production-baseline"
}
AFT picks this up, provisions the account through Control Tower, and runs account customizations — setting up VPC peering, deploying security baselines, configuring logging, and applying the right SCPs.
Control Tower Guardrails
I layer compliance frameworks as Security Hub standards:
- AWS Foundational Security Best Practices (FSBP) — the baseline for every account
- CIS AWS Foundations Benchmark — for accounts handling sensitive data
- NIST 800-53 — public sector requirements
- SOC2 — mapped through custom Config rules where Security Hub doesn’t cover it natively
SCPs enforce the hard boundaries: no disabling CloudTrail, no leaving the approved regions, no creating IAM users (Identity Center only).
Terraform Module Strategy
Across the pharma project I built and maintained 20+ reusable Terraform modules covering networking (VPC, Transit Gateway, Route 53), compute (ECS, Lambda), data (RDS, S3, DynamoDB), security (IAM, KMS, WAF), observability (CloudWatch, CloudTrail), and DR (AWS Backup cross-account vaulting).
Every module follows the same conventions: consistent variable naming, outputs that other modules can consume, and documentation generated by pre-commit hooks.
State Management
State lives in S3 with DynamoDB locking. Cross-account deployments use IAM roles assumed through Identity Center for interactive work and OIDC federation for CI/CD:
terraform {
backend "s3" {
bucket = "org-terraform-state"
key = "workloads/project-alpha/prod/networking.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
role_arn = "arn:aws:iam::role/TerraformStateAccess"
}
}
Each workload gets its own state key path. No monolithic state files, no cross-team lock contention.
Pre-Commit Hooks
Every Terraform repo runs pre-commit hooks before code hits the remote:
terraform fmt— consistent formattingterraform validate— syntax and provider validationterraform-docs— auto-generated module documentationtflint— linting for provider-specific issuescheckov— static security analysis
This catches 90% of issues before a pipeline even runs. The remaining 10% gets caught by terraform plan in CI.
The Pattern Works
Whether it’s 10 accounts or 40+, the structure is the same. AFT for provisioning, Control Tower for guardrails, reusable modules for consistency, and strict state isolation per workload. The initial setup takes effort, but after that, spinning up a new compliant account is a pull request away.