← All How-to Guides
AWS Control Tower Account Factory Automation Organizations

How to Automate AWS Account Vending with Control Tower Account Factory

Use the Control Tower Account Factory to create new AWS accounts programmatically — with the right baseline, SSO access, and VPC configuration from day one

Advanced ⏱ 60 min

Prerequisites

  • AWS Control Tower landing zone deployed
  • IAM Identity Center configured
  • Git repository for Account Factory customizations (optional)
  • Admin access to the Control Tower management account
  • Unique email address available for each new account

I’ve watched teams manually create AWS accounts by filling out form after form, copying and pasting account IDs, and then separately configuring VPCs, baselines, and permission sets. It was error-prone and slow. Control Tower’s Account Factory—especially when paired with Account Factory for Terraform—turns account creation into a self-service, auditable, repeatable process. This guide covers both the quick path (console) and the production path (Terraform).

Understand the Three Account Factory Options

Control Tower offers three ways to spin up new accounts. Choose the one that fits your organization’s automation maturity:

1. AWS Console (Account Factory UI)

  • Fastest to implement, no infrastructure required
  • Manual account creation, one at a time
  • Good for teams creating a few accounts per month
  • Full guardrails and baselines applied automatically

2. Account Factory for Terraform (AFT)

  • GitOps-driven, code is the source of truth
  • Programmatic account creation via Terraform modules
  • Supports bulk account creation with Terraform loops
  • Excellent for teams managing 50+ accounts
  • Integrates with your existing CI/CD pipelines

3. Service Catalog Direct

  • API-driven, suitable for custom integrations
  • Can be called from Lambda, Step Functions, or applications
  • Best for platforms that need a REST API for account creation
  • Requires more custom integration work

Start with the console for testing, then graduate to AFT for production workloads.

Create an Account Via Console (Quickest Path)

This is the fastest way to verify your setup works. In the AWS Management Console, navigate to AWS Control TowerAccount FactoryCreate account.

You’ll fill out a form with:

  • Account email: Must be globally unique across AWS (e.g., my-app-prod@company.com)
  • Account name: Human-readable identifier (e.g., prod-payment-api)
  • Organization unit: Select the OU where this account belongs (e.g., Workloads/Production)
  • SSO user email: The email address of the user who will have initial SSO access
  • (Optional) Custom VPC configuration: Choose whether to deploy the default VPC

Click Create account. Control Tower now orchestrates:

  1. Creating the account in AWS Organizations
  2. Enrolling it in Control Tower
  3. Applying all mandatory guardrails
  4. Deploying VPCs if configured
  5. Creating the Landing Zone CloudFormation stack

The process takes 20–30 minutes. Watch the Account Factory events tab to monitor progress. Errors appear here if something fails (e.g., email already in use).

Note: The account email must be unique globally across AWS. Use email aliases with + to reuse your domain (e.g., ops+proj-a-prod@company.com, ops+proj-b-prod@company.com).

Set Up Account Factory for Terraform (AFT)

AFT is a managed service that provisions AWS infrastructure for you (CodePipeline, CodeBuild, DynamoDB). It then watches a Git repository and creates accounts when you commit new Terraform files.

First, clone the AFT GitHub repository:

git clone https://github.com/aws-ia/terraform-aws-control_tower_account_factory.git
cd terraform-aws-control_tower_account_factory

Create an aft-management AWS account (separate from your Control Tower management account). This account hosts the AFT automation infrastructure. Generate temporary credentials for that account and deploy the AFT Terraform module:

cd modules/aft-management
terraform init
terraform plan
terraform apply

This creates:

  • CodePipeline and CodeBuild projects to process account requests
  • DynamoDB table to track account creation state
  • IAM roles with permissions to create accounts
  • Codebuild for running customizations in new accounts

The deployment takes 5–10 minutes. You’ll see outputs with the Git repository URLs you need to push to.

Create an Account Via AFT (GitOps)

Clone the three AFT repositories created during setup:

  1. aft-account-requests — where you define new accounts
  2. aft-account-customizations — baseline resources for every account
  3. aft-global-customizations — organization-wide configurations

To request a new account, create a Terraform file in aft-account-requests:

# aft-account-requests/accounts/prod-datapipeline.tf

module "prod_datapipeline" {
  source = "./modules/aft-account-request"

  control_tower_parameters = {
    AccountEmail              = "aws+prod-datapipeline@company.com"
    AccountName               = "prod-datapipeline"
    ManagedOrganizationalUnit = "Workloads/Production"
  }

  account_tags = {
    Environment         = "Production"
    Owner               = "DataEngineering"
    CostCenter          = "CC-1234"
    Application         = "DataPipeline"
    DataClassification  = "Confidential"
  }

  terraform_token       = var.terraform_token
  terraform_version     = "1.5.0"
  concurrent_deployment = 5
}

Commit and push to aft-account-requests:

git add .
git commit -m "Add prod-datapipeline account"
git push origin main

CodePipeline detects the commit and launches a CodeBuild job. It parses your Terraform, calls Control Tower’s Account Factory to create the account, and monitors provisioning. You can watch progress in the CodePipeline console or in the DynamoDB table.

Add Account Customizations (Baselines)

Most organizations want new accounts to start with standard resources: VPC with standard CIDR blocks, security baselines, CloudWatch log groups, and IAM roles. AFT runs these customizations automatically via CodeBuild.

In the aft-account-customizations repository, create files for resources you want in every account:

# aft-account-customizations/global/terraform/main.tf

resource "aws_vpc" "main" {
  cidr_block = "10.${data.aws_caller_identity.current.account_id % 256}.0.0/16"

  tags = {
    Name = "${var.account_name}-vpc"
  }
}

resource "aws_subnet" "private" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.${data.aws_caller_identity.current.account_id % 256}.${count.index * 16}.0/20"
  availability_zone = data.aws_availability_zones.available.names[count.index % 3]

  tags = {
    Name = "${var.account_name}-private-${count.index + 1}"
  }
}

resource "aws_cloudtrail" "account" {
  name                          = "${var.account_name}-trail"
  s3_bucket_name                = var.central_log_bucket
  include_global_service_events = true
  is_multi_region_trail         = true
  enable_log_file_validation    = true

  depends_on = [aws_s3_bucket_policy.cloudtrail_policy]
}

When a new account is created, AFT automatically runs this Terraform in the new account, deploying your baseline configuration.

Verify the New Account and Check Guardrails

Once the account is provisioned, verify it:

# List all accounts in your organization
aws organizations list-accounts --query 'Accounts[].{Name:Name,Id:Id,Status:Status}'

# Check that the new account exists
aws organizations describe-account --account-id 123456789012

Assume into the new account to verify the baseline:

# Assume the Control Tower execution role in the new account
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/AWSControlTowerExecution \
  --role-session-name verify-account

# Export the temporary credentials from the output
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...

# Verify guardrails are applied
aws controltower list-enabled-controls --target-identifier arn:aws:organizations::MGMT:ou/o-xxx/ou-xxx

# Verify baselines deployed
aws ec2 describe-vpcs
aws cloudtrail describe-trails

Handle Common Failures

Email already exists globally: AWS requires globally unique account emails. Use + aliases:

# Instead of:
# AccountEmail = "myteam-prod@company.com"

# Use:
# AccountEmail = "myteam+prod@company.com"

Account quota exceeded: AWS limits new accounts to 10 per 24 hours by default. Request a quota increase via Service QuotasAWS OrganizationsCreate Account.

Config recorder conflict: If your baseline Terraform creates a Config recorder but one already exists, AFT fails. Check for pre-existing recorders:

aws configservice describe-configuration-recorders

If found, either delete it or skip it in your customization code.

VPC CIDR conflict: If your customization creates a VPC but that CIDR is already in use in your Transit Gateway, update your customization to use a calculated CIDR based on account ID (as shown above).

# Monitor AFT execution in CodeBuild logs
aws codebuild batch-get-builds --ids aft-AccountRequest-prod-datapipeline-v1

Is This Safe?

Account creation is additive and non-destructive. AFT is idempotent—re-running it with the same Terraform configuration won’t create duplicate accounts. The most sensitive operation is the initial AFT deployment in your aft-management account, which requires AdministratorAccess. Restrict who can merge pull requests to your aft-account-requests repo. Git audit logs show who created which account and when.

Key Takeaway

Control Tower Account Factory eliminates manual account provisioning. The console is great for learning; AFT is the production approach for teams managing dozens or hundreds of accounts. With AFT, new accounts are provisioned with guardrails, baselines, and all the right permissions in 20 minutes. Your infrastructure becomes code, your account creation is auditable, and your team self-serves without waiting on a central platform team.

Questions? Connect with me on LinkedIn.