AWS Organizations - The Foundation of Multi-Account

AWS Organizations - The Foundation of Multi-Account architecture diagram

AWS Organizations is the backbone of any multi-account setup. Before any IaC can run, you need to solve a bootstrap chicken-and-egg problem.

This post covers the Organizations setup, the bootstrap problem, and how to structure accounts and organizational units for a small-to-medium deployment.

The Bootstrap Chicken-and-Egg

Before any Terraform or OpenTofu code runs, you need a management account. This is the one genuinely manual step - there's no API for creating an AWS account from nothing. You'll go through the AWS account creation process which requires an email address, credit card, phone verification, and choosing a support plan (Free tier is fine to start).

The good news: AWS offers a Free Tier that covers a surprising amount of what you'll use during bootstrap. The bad news: not everything in this series is free, and some costs can catch you off guard.

Once that account exists and you have credentials configured, everything else can be managed as code - including creating the organization itself via the aws_organizations_organization resource. But you still need to make some practical decisions about bootstrapping order:

  1. Create the management account manually (the real chicken-and-egg)
  2. Configure credentials (IAM user or SSO - whatever gets you started)
  3. Set up a state backend (even a local state file works initially)
  4. Run your IaC to create the organization, OUs, and member accounts

Treat root as emergency-only after bootstrap. Day-to-day access should go through IAM Identity Center permission sets.

What This Series Will Cost You

Here's what this multi-account setup actually costs. Not everything is Free Tier eligible, and some services will start billing from day one.

Free across this series:

ServiceUsed inWhy it's free
AWS OrganizationsPart 2Always free
IAM (users, roles, policies)All partsAlways free
STS (role assumption, OIDC)Parts 5, 8Always free
IAM Identity CenterParts 6, 7Free with built-in identity store
ACM public certificatesPart 10Public certs are free

Free Tier eligible (watch the limits):

ServiceUsed inFree Tier allowanceWhat to watch
S3Parts 3, 45 GB storage, 2,000 PUT and 20,000 GET requests/monthTerraform runs generate more requests than you'd expect - every plan and apply reads and writes state. CloudTrail logs add up too
DynamoDBPart 425 GB storage, 25 WCU/25 RCUState locking is lightweight, unlikely to exceed
CloudTrailPart 3First trail per region (management events only)One organization trail is typically enough
CloudWatch LogsPart 35 GB ingestion/monthOnly a concern if you enable DNS query logging

Costs money from the start:

ServiceUsed inCostNotes
Route 53 hosted zonesPart 10$0.50/zone/monthEach domain needs a zone. Two zones = $1/month
Route 53 DNS queriesPart 10$0.40/million queriesNegligible for low-traffic sites
AWS ConfigPart 3$1/account/month + $0.001/rule evaluationAdds up across multiple accounts

The S3 surprise: During initial setup, Terraform generates a lot of S3 API requests. Every tofu plan reads the state file, every tofu apply writes it back, and DynamoDB gets a lock/unlock cycle each time. When you're iterating on infrastructure code - running plan-apply dozens of times while getting things right - those 2,000 free PUT requests can disappear faster than you'd think. It won't break the bank (overage is $0.005 per 1,000 requests), but it's worth knowing it's not truly "free" during active development.

Realistic total: For the complete setup across this series, expect $3-10/month for a small organisation. Route 53 and AWS Config are the main drivers. Everything else is either free or costs pennies.

Understanding the Hierarchy

AWS Organizations creates a tree structure:

aws-account-structure-part-2-organizations/org-hierarchy diagram
Click to expand
846 × 416px

Root: The top of the hierarchy. Every organization has exactly one root. You can't delete it or create another.

Organizational Units (OUs): Containers for grouping accounts. OUs can be nested, though we keep them flat for simplicity in this case.

Accounts: The actual AWS accounts where resources live. Each account belongs to exactly one OU (or the root directly).

Management Account: The account where you created the organization. It has special privileges and restrictions - never run workloads here.

OU Structure

We use three organizational units:

hcl
organizational_units = { "Production" = {} "Infrastructure" = {} "Security" = {} }

Production: Accounts running customer-facing workloads. The website production account lives here.

Infrastructure: Shared services like DNS. The DNS management account lives here.

Security: Security-focused services and accounts (for example log archive, delegated security tooling, and guardrail-related infrastructure). Even if initially light on workloads, this OU creates a clean boundary for future security centralization.

Some organisations also create OUs for Development, Staging, and Sandbox. That's valid for larger teams - see the recommended OU structure in AWS's multi-account whitepaper. For this setup, Production, Infrastructure, and Security provide a practical baseline.

Creating the Organization Resource

Here's the OpenTofu configuration that manages Organizations:

hcl
resource "aws_organizations_organization" "org" { feature_set = "ALL" enabled_policy_types = [ "SERVICE_CONTROL_POLICY" ] aws_service_access_principals = [ "sso.amazonaws.com", "cloudtrail.amazonaws.com", "config.amazonaws.com", "iam.amazonaws.com", "account.amazonaws.com" ] }

feature_set = "ALL": Enables all Organizations features, not just consolidated billing. Required for SCPs and delegated administration.

enabled_policy_types: We enable Service Control Policies even though they're not heavily used yet. It's easier to enable upfront than retrofit later.

aws_service_access_principals: These allow AWS services to operate across your organization. Each one enables specific cross-account functionality:

Service principalPurpose
sso.amazonaws.comIAM Identity Center integration
cloudtrail.amazonaws.comOrganization-wide CloudTrail
config.amazonaws.comCross-account AWS Config
iam.amazonaws.comCentralized root access management
account.amazonaws.comAWS Account Management APIs

Creating Organizational Units

OUs are straightforward:

hcl
resource "aws_organizations_organizational_unit" "ous" { for_each = var.organizational_units name = each.key parent_id = data.aws_organizations_organization.org.roots[0].id tags = { Name = each.key ManagedBy = "OpenTofu" } }

The parent_id references the organization root. For nested OUs, you'd reference another OU's ID instead.

Account Vending

Creating new AWS accounts through Organizations is called "account vending". Here's the configuration:

hcl
resource "aws_organizations_account" "accounts" { for_each = var.accounts_to_create name = each.key email = each.value.email role_name = "OrganizationAccountAccessRole" iam_user_access_to_billing = "DENY" close_on_deletion = false parent_id = aws_organizations_organizational_unit.ous[each.value.organizational_unit].id lifecycle { prevent_destroy = true } }

Key points:

email: Each AWS account needs a unique email address. We use email aliases like aws-website-prod@example.com.

role_name: Organizations automatically creates this IAM role in the new account, allowing the management account to assume it. This is how cross-account management works initially.

iam_user_access_to_billing = "DENY": Prevents IAM users from accessing billing. Only the root user (and Identity Center users with billing permissions) can see costs.

prevent_destroy = true: Deleting an AWS account is a significant action. This lifecycle rule requires manual intervention.

Importing Existing Accounts

The DNS account existed before Organizations was set up. It was imported into the organization rather than created fresh:

hcl
# Import existing account import { to = aws_organizations_account.imported["dns"] id = "111111111111" # Example existing account ID }

Importing accounts is straightforward, but there's a catch: you can't change the email address or account name after import. Plan your naming carefully.

What the Management Account Should (and Shouldn't) Do

The management account has special powers:

ItemKeep in management account?Why
AWS Organizations configurationYesThis is the organization control plane
IAM Identity Center (or delegated admin setup)YesIdentity control belongs at org level
Organization CloudTrail trailYesCentral audit ownership
Terraform state backendYesShared control-plane dependency
CI/CD cross-account rolesYesCommon trust anchor for deployments
Production workloadsNoManagement account is not covered by SCPs
Development resourcesNoReduces attack surface and operational noise
Anything internet-facing or high-riskNoCompromise impact is organization-wide

The management account can't have SCPs applied to it - that's an AWS protection. If someone compromises this account, they own your entire organization. Keep it locked down.

Consolidated Billing

Organizations automatically consolidates billing across all member accounts. You get a single invoice, and each account's costs appear as a separate line item - so you can see exactly what the website production account's Amplify hosting costs versus the DNS account's Route 53 charges.

You also get volume discounts from aggregated usage, tag-based cost allocation, and Reserved Instance sharing across accounts. There's nothing to enable - it's automatic once accounts join the organisation.

Common Mistakes

Running workloads in the management account: Don't. Create a separate account even for personal projects. See AWS's multi-account best practices for more guidance.

Too many OUs too early: Start simple. You can always add OUs later. Refactoring OU structure is easier than maintaining unnecessary complexity.

Forgetting email uniqueness: Each account needs a unique email. Set up a convention (aws-{purpose}@yourdomain.com) before you need it.

Not enabling all features: If you only enable consolidated billing, you can't use SCPs or delegated administration later without migrating.

What's Next

With Organizations in place, the next step is establishing security foundations: CloudTrail for audit logging, AWS Config for compliance monitoring, and IAM password policies.

These services are set up in the bootstrap repository and give the whole organisation its security baseline.


← Back to all posts