AWS for DevOps — Multi-Account Strategy

4 minute read

Why Multiple AWS Accounts?

In production environments, running everything in a single AWS account is a security and operational risk. Multi-account strategies provide blast radius isolation, billing separation, and fine-grained access control.

Real-World Analogy: Running dev, staging, and prod in one AWS account is like keeping your personal and business finances in the same bank account — it works until something goes wrong, and then it’s a disaster to untangle.


The Problem with Single Accounts

Risk Example Impact
Blast radius Dev mistake deletes prod database Catastrophic
Cost confusion Can’t separate dev vs prod spending Budget chaos
Permission creep Dev team has prod access Security breach
Service limits Dev consumes all EC2 limits Prod can’t scale
Compliance Audit scope includes all environments Expensive audits

AWS Organizations

AWS Organizations lets you manage multiple AWS accounts from a single management account.

┌──────────────────────────────────────────────────────────────┐
│                  AWS Organizations                            │
│                                                              │
│  ┌─────────────────────────────────────────────────────────┐│
│  │  Management Account (Root)                               ││
│  │  - Billing consolidation                                 ││
│  │  - Organization policies                                 ││
│  │  - NO workloads here                                     ││
│  └───────────────┬─────────────────────────────────────────┘│
│                   │                                          │
│    ┌──────────────┼──────────────┐                          │
│    │              │              │                           │
│  ┌─┴──────┐  ┌───┴────┐  ┌────┴─────┐                     │
│  │Security │  │Workload│  │Sandbox   │   ← OUs             │
│  │   OU    │  │  OU    │  │  OU      │                     │
│  └────┬────┘  └───┬────┘  └────┬─────┘                     │
│       │           │            │                             │
│  ┌────┴────┐ ┌────┴────────┐ ┌┴────────┐                   │
│  │Log      │ │Dev  Staging │ │Developer │                   │
│  │Archive  │ │Prod         │ │Sandboxes │                   │
│  │Security │ │             │ │          │                   │
│  │Tooling  │ │             │ │          │                   │
│  └─────────┘ └─────────────┘ └──────────┘                   │
└──────────────────────────────────────────────────────────────┘

Minimum Viable Multi-Account (Startups)

Account Purpose Who Uses It
Management Billing, Organizations Account admins only
Security CloudTrail logs, GuardDuty, Security Hub Security team
Dev Development workloads Developers
Prod Production workloads CI/CD pipelines

Enterprise Account Structure

OU Accounts Purpose
Security OU Log Archive, Security Tooling, Audit Centralized security
Infrastructure OU Networking, Shared Services Transit Gateway, DNS
Workloads OU Dev, Staging, Prod (per team) Application workloads
Sandbox OU Individual developer accounts Experimentation
Suspended OU Decommissioned accounts Account graveyard

Setting Up AWS Organizations

# Create the organization
$ aws organizations create-organization --feature-set ALL
{
    "Organization": {
        "Id": "o-abc123",
        "MasterAccountId": "111111111111"
    }
}

# Create Organizational Units
$ aws organizations create-organizational-unit \
    --parent-id r-root \
    --name "Security"
{
    "OrganizationalUnit": {
        "Id": "ou-security",
        "Name": "Security"
    }
}

$ aws organizations create-organizational-unit --parent-id r-root --name "Workloads"
$ aws organizations create-organizational-unit --parent-id r-root --name "Sandbox"

# Create member accounts
$ aws organizations create-account \
    --email prod-aws@company.com \
    --account-name "Production"
{
    "CreateAccountStatus": {
        "Id": "car-abc123",
        "AccountId": "222222222222",
        "State": "IN_PROGRESS"
    }
}

# Move account to correct OU
$ aws organizations move-account \
    --account-id 222222222222 \
    --source-parent-id r-root \
    --destination-parent-id ou-workloads

Service Control Policies (SCPs)

SCPs set maximum permission boundaries for all accounts in an OU.

Deny Leaving the Organization

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyLeaveOrg",
    "Effect": "Deny",
    "Action": "organizations:LeaveOrganization",
    "Resource": "*"
  }]
}

Restrict Regions

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyNonApprovedRegions",
    "Effect": "Deny",
    "NotAction": [
      "iam:*",
      "organizations:*",
      "support:*",
      "sts:*"
    ],
    "Resource": "*",
    "Condition": {
      "StringNotEquals": {
        "aws:RequestedRegion": ["us-east-1", "us-west-2", "eu-west-1"]
      }
    }
  }]
}

Deny Root User Access (Non-Management Accounts)

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyRootUser",
    "Effect": "Deny",
    "Action": "*",
    "Resource": "*",
    "Condition": {
      "StringLike": {
        "aws:PrincipalArn": "arn:aws:iam::*:root"
      }
    }
  }]
}
# Apply SCP to an OU
$ aws organizations create-policy \
    --name "DenyRootAccess" \
    --description "Deny root user in member accounts" \
    --type SERVICE_CONTROL_POLICY \
    --content file://deny-root.json

$ aws organizations attach-policy \
    --policy-id p-abc123 \
    --target-id ou-workloads

AWS Control Tower

Control Tower automates multi-account setup with best practices built in.

Feature What It Does
Landing Zone Pre-configured multi-account environment
Guardrails Preventive and detective controls
Account Factory Self-service account creation
Dashboard Compliance visibility

Control Tower vs Manual Setup

Aspect Manual (Organizations) Control Tower
Setup time Days to weeks Hours
Best practices You define them Built-in
Account creation CLI/Console Account Factory
Compliance Custom monitoring Automatic guardrails
Drift detection Custom Built-in
Best for Custom requirements Standard enterprise

Cross-Account Access Patterns

Pattern 1: IAM Role Assumption

# In the target account (Prod), create a role
$ aws iam create-role \
    --role-name CrossAccountDeployRole \
    --assume-role-policy-document '{
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {
          "AWS": "arn:aws:iam::111111111111:root"
        },
        "Action": "sts:AssumeRole",
        "Condition": {
          "StringEquals": {
            "sts:ExternalId": "deploy-2026"
          }
        }
      }]
    }'

# From the source account, assume the role
$ aws sts assume-role \
    --role-arn arn:aws:iam::222222222222:role/CrossAccountDeployRole \
    --role-session-name deploy-session \
    --external-id deploy-2026

Pattern 2: Resource-Based Policies

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "CrossAccountS3Access",
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::111111111111:role/CI-CD-Role"
    },
    "Action": ["s3:GetObject", "s3:PutObject"],
    "Resource": "arn:aws:s3:::prod-artifacts/*"
  }]
}

Consolidated Billing

Feature Benefit
Single bill One invoice for all accounts
Volume discounts Combined usage gets better pricing
Reserved Instance sharing RI benefits apply across accounts
Cost allocation tags Track spending per team/project

Common Mistakes

Mistake Impact Prevention
Running workloads in management account Security risk Use member accounts only
No SCPs Accounts can do anything Apply restrictive SCPs from day one
Shared VPCs without planning Networking complexity Design CIDR ranges upfront
No centralized logging Can’t investigate incidents Set up Log Archive account first
Manual account creation Inconsistent setups Use Account Factory or IaC

Next → Well-Architected Framework

Updated: