Account Factory for Terraform (AFT) sounds like a landing zone. It isn't one. AFT is a Terraform pipeline that vends and customizes accounts inside an AWS Control Tower landing zone you already run. Weighing it against a CDK landing zone means comparing a pipeline on top of a foundation with the foundation itself.
That mismatch stays hidden until the first accounts exist. Then the questions start. Why did a push to the customizations repository change nothing? How do you close an account you deleted from the request repository?
I build and maintain the CDK landing zone we deploy for engineering teams, so weigh my bias accordingly. Neither tool wins outright: they serve different engineers. The nine trade-offs below, from where a change gets reviewed to who owns upgrades, show which one fits yours.
AFT vs CDK Landing Zone: The Short Answer
AFT is an AWS-maintained Terraform module that adds an account-vending and customization pipeline to Control Tower. It needs a Control Tower landing zone and a dedicated AFT management account, and it creates a pipeline for every account it vends. It doesn't replace Control Tower's account machinery; AWS's AFT provisioning guide says: "If you provision an account through AFT or Account Factory, the same backend workflow occurs."
A CDK landing zone is the foundation itself. The organization, OUs, accounts, service control policies (SCPs), account baselines, and offboarding live as CDK code in your repository, and Control Tower is never enabled.
- Pick AFT if your team already works in Terraform and runs Control Tower.
- Pick a CDK landing zone if your team works in CloudFormation or CDK, or you'd rather not depend on Control Tower. It builds on AWS Organizations and StackSets directly. Control Tower is a managed layer whose resources AWS tells you not to modify or delete.
- Weighing more than these two? Start with the five AWS-native alternatives compared.
What Account Factory for Terraform Adds to Control Tower (and Needs First)
Does AFT require Control Tower? Yes. The Deploy AFT guide lists a Control Tower landing zone in a home Region as the first prerequisite. The rest of the list:
- An AFT management account, separate from the Control Tower management account, provisioned or enrolled through Control Tower.
- Terraform 1.6.1 or later, on Terraform Community Edition, HCP Terraform, or Terraform Enterprise (supported versions).
- A version control system (VCS) and a runtime environment that can run
terraform applyfor the module. - A target organizational unit (OU) with
AWSControlTowerBaselineenabled (a provisioning prerequisite).
Day to day you work in four repositories: aft-account-request (one Terraform file per account), aft-account-provisioning-customizations (a Step Functions state machine that runs first), aft-global-customizations (Terraform for every AFT account), and aft-account-customizations (named customizations accounts opt into). Behind them, the AFT management account runs its own stack of services:
- CodePipeline and CodeBuild for every Terraform run
- Step Functions and Lambda for orchestration
- DynamoDB for requests, audit history, and account metadata
- An SQS FIFO queue in front of Control Tower Account Factory
- S3 state buckets in a primary and an optional secondary Region, plus KMS keys and a VPC
- EventBridge rules in both management accounts for Control Tower lifecycle events
The VCS can be CodeCommit (the module default) or GitHub, GitHub Enterprise Server, Bitbucket, GitLab, GitLab Self-managed, or Azure DevOps through AWS CodeConnections. A third-party provider ends setup with a manual step: approving the pending connection in the AFT management account console.
The current release is 1.23.0, published on 2026-10-23 on the Terraform Registry. The Control Tower team maintains the module, and you install it with your own terraform apply.
The CDK landing zone starts one layer lower. Its install needs a management account and a GitHub repository, and it creates the organization if none exists. If Control Tower itself is still the open question, settle Control Tower's own trade-offs first.
Where Does a Change Get Reviewed: terraform plan or cdk diff?
Your existing IaC is the first filter: Terraform teams get the most from AFT, CloudFormation and CDK teams from the CDK landing zone. The HCL vs TypeScript decision has its own article. Beyond language, what matters here is where a reviewer sees a change's effect on member accounts before it applies.
In AFT, pushing an edited account request "invokes the account provisioning workflow". Pushing to a customizations repository doesn't. The re-invoke documentation is blunt about it: "At this point, nothing happens." You then start the aft-invoke-customizations state machine in the AFT management account and tell it which accounts to target.
Since 1.22.0 you can add "plan_only": true to that input, and AFT runs terraform plan instead of terraform apply. On Community Edition, the plan JSON goes to an encrypted S3 bucket in the AFT management account by default. On HCP Terraform and Terraform Enterprise, export is opt-in through aft_plan_output_export_enabled, and the run shows in the HCP Terraform UI. AFT's docs describe those two places for plan output and no pull request comment, so getting a plan in front of code reviewers is yours to build.
The CDK landing zone puts the review in the pull request. Every pull request against main runs offline validation, then organization:diff against the management account and landingzone:diff:all against the landing zone account. One bot comment collects both.
A plain cdk diff shows a StackSet change only as a new template URL on the parent stack. These diff tasks also compare each StackSet's template with the one it runs, so the comment lists the resources and IAM statements that change in member accounts. Merging to main deploys both phases through GitHub Actions: the organization phase in the management account, then the landing-zone phase.
My test: pick your last risky baseline change and ask where the reviewer saw its effect. If the answer is "in the pull request," the CDK model matches how your team works. If you review Terraform plans in HCP Terraform today, AFT's plan-only runs fit that habit.
Vending an Account: Request File vs Config Entry
Both tools vend accounts from a file in Git, in different shapes.
An AFT account request calls AFT's account-request module. Its control_tower_parameters block carries the six fields Control Tower needs, per Provision a new account with AFT:
module "payments_prod" {
source = "./modules/aft-account-request"
control_tower_parameters = {
AccountEmail = "aws+payments-prod@example.com"
AccountName = "payments-prod"
ManagedOrganizationalUnit = "Production"
SSOUserEmail = "platform@example.com"
SSOUserFirstName = "Platform"
SSOUserLastName = "Team"
}
account_tags = { team = "payments" }
change_management_parameters = {
change_requested_by = "platform-team"
change_reason = "New payments production account"
}
custom_fields = { data_classification = "restricted" }
account_customizations_name = "production"
}
Of those six fields, only ManagedOrganizationalUnit can change later. Each custom_fields entry becomes an SSM parameter under /aft/account-request/custom-fields/ in the new account for your customizations to read. Control Tower runs up to 5 concurrent account operations by default, adjustable to a maximum of 10 (Control Tower quotas).
In the CDK landing zone, a new account is a four-line entry in its OU's accounts map in src/config/organization-structure.ts:
PaymentsAccount: {
name: 'Payments',
email: 'aws+payments-prod@example.com',
},
pnpm run organization:diff shows one new AWS::Organizations::Account in LandingZoneOrganizationStack, and merging deploys it. The baseline arrives through StackSets with service-managed permissions, which CloudFormation deploys through AWS Organizations. They target OUs, not account lists, so the stack instances deploy as soon as the account joins its OU. You confirm they reach CURRENT in the CloudFormation console, as the add-an-account guide shows.
What Happens After the Account Exists: Day-2 Changes and Drift
The months after vending show how each tool handles change.
To change one AFT account, edit its request and push. Fleet-wide changes go through the re-invoke path, with filters of type all, ous, tags, or accounts. This input targets every production account except one:
{
"include": [{ "type": "ous", "target_value": ["Production"] }],
"exclude": [{ "type": "accounts", "target_value": ["111122223333"] }]
}
By default, AFT runs at most 5 customizations at a time, and the state machine loops until every matching account is done (AWS Cloud Operations blog). Since 1.21.0 you can also opt in to re-running customizations when an account changes OU, with aft_customization_triggers = ["account_move"].
Drift is where the scopes diverge. AFT 1.23.0 runs customizations on events and checks account requests every 5 minutes. It has no scheduled customization run and no Terraform drift detection, so AFT itself doesn't flag a console edit to a customized resource until a later run touches it.
Control Tower watches its own layer for governance drift. A drifted landing zone blocks Enroll account. Moving an account between OUs with the Organizations MoveAccount API creates moved-account drift unless auto-enrollment is on and both OUs match.
The CDK landing zone handles membership through those OU-targeted StackSets: an account that joins a targeted OU gets the stack instances, and one that leaves loses them. A daily job runs CloudFormation drift detection on the landing zone's own stacks and StackSets in the management and landing zone accounts.
Two guardrails hold that layer to the repository. The protectTaggedCloudFormationStacksSCP denies updating or deleting stacks tagged cdk-landing-zone:managed to everyone except landing zone administrators. And declared accounts move only through the config file: move one in the Organizations console and the next update to that account's resource, such as a tag change, moves it back.
The limit on the CDK side: that drift detection covers landing-zone-owned stacks. Workload Terraform or CDK in member accounts sits outside it, as it does for AFT.
Closing an Account: Does Deleting the Entry Close It?
Removing an account from AFT starts with deleting its account request and pushing. AFT then deletes the account's customization pipeline and metadata, per Remove an account from AFT. The rest is manual:
- On HCP Terraform or Terraform Enterprise, delete the global and account customizations workspaces.
- Delete the account's Terraform state folders from the backend S3 buckets in the AFT management account. On Community Edition that's 3 folders per account in each backend bucket.
- Unmanage the account in Control Tower, one account at a time, because "AWS Control Tower still manages the account."
- Close it through AWS Organizations or the Billing console.
AWS warns that removal "is irreversible and can result in a loss of state."
Deleting the entry doesn't close an account in the CDK landing zone either, because AWS Organizations has no programmatic account deletion. The difference is what you get for closing. You move the entry under CloseAccountOU in a pull request. After the deploy, the account inherits the lockdownSuspendedAccountsSCP, and Security Hub CSPM, GuardDuty, and Inspector turn off there.
The organization phase then closes active accounts in that OU every 8 hours, retrying failures on the next run. Once the account shows as closed, you delete its entry. If you need to investigate before closing, SuspendedOU quarantines an account and keeps detection running.
Two AWS limits apply whichever tool you choose. Organizations lets you close the higher of 250 or 20% of member accounts in a rolling 30 days, up to 1,000, and you can't raise that quota. A closed account then sits in SUSPENDED for 90 days, and AWS can still bill resources left inside it. For the console route, see removing and closing accounts by hand.
Will It Run in Your Regions, and What Does It Cost to Leave Running?
AFT supports fewer Regions than Control Tower, and AWS keeps two separate lists of gaps on its limitations and quotas page.
As of October 2026, AFT isn't available at all in 10 Regions, because dependencies are missing: Europe (Zurich), Europe (Spain), Canada West (Calgary), Asia Pacific (Melbourne), Asia Pacific (Malaysia), Asia Pacific (Thailand), Mexico (Central), Asia Pacific (New Zealand), Asia Pacific (Taipei), and the AWS European Sovereign Cloud (Germany).
New AFT customers also can't deploy in 13 Regions where CodeConnections doesn't support third-party VCS, including Hong Kong, Cape Town, and Bahrain. AFT deploys in your Control Tower home Region, so check that Region against both lists first.
The CDK landing zone sets its Regions in configuration: a primaryRegion plus secondaryRegions, at most 15 in total. A region-guardrail SCP denies regional API calls outside them.
How much does AFT cost to run? Per AWS's AFT cost page, AFT itself is free, but "the default AFT configuration includes the allocation of AWS PrivateLink endpoints ... and a NAT gateway that is required to support AWS CodeBuild." AWS publishes no monthly figure, so here is my estimate for the default VPC at module v1.23.0, idle in us-east-1:
| Line item | Default count | us-east-1 rate | Monthly at 730 hours |
|---|---|---|---|
| NAT gateways | 2, one per AZ | $0.045 per hour each | $65.70 |
| Interface endpoints | Up to 15, across 2 AZs | $0.01 per hour per endpoint ENI | $219.00 |
| Estimate before traffic | about $285 |
It assumes NAT gateway and PrivateLink rates from the AWS Price List for us-east-1, every endpoint in both AZs, and zero traffic. Data processing and the Elastic IPs on the 2 NAT gateways come on top. Outside us-east-1 the module creates 14 interface endpoints, since it adds the Organizations endpoint only there.
Setting aft_enable_vpc = false deploys AFT "without the use of a VPC and private networking resources, such as NAT Gateways or VPC endpoints," which AWS says "may help reduce the operating cost of AFT for some usage patterns." Other drivers stay either way, such as CodeBuild runs, log groups, and KMS keys. All of it sits on top of what Control Tower's Config and CloudTrail cost.
On the CDK side, the measured running cost of a CDK landing zone was $59.17/month for the core landing zone with optional security services off, across 9 member accounts in August 2026 on an earlier architecture. That covers the core landing zone, while the AFT estimate covers the pipeline's VPC alone. The CDK pipeline runs on GitHub Actions, so it puts no pipeline VPC in AWS.
Who Owns the Upgrades?
AFT is AWS's module but your deployment, so every upgrade is yours to run. The update guide has you sign in to the Control Tower management account, run terraform get -update, then plan and apply.
The cadence adds up. Six minor versions, 1.18.0 through 1.23.0, shipped between December 2025 and October 2026, and 3 of them can break an upgrade (release notes):
| Version | Published | Upgrade impact |
|---|---|---|
| 1.18.0 | 2025-12-22 | Minimum AWS provider 6.0.0 |
| 1.20.0 | 2026-04-24 | Minimum Terraform 1.6.1 |
| 1.23.0 | 2026-10-23 | CodeBuild moves from Amazon Linux 2 to Amazon Linux 2023; check pre-api-helpers.sh and post-api-helpers.sh |
AWS Prescriptive Guidance recommends pinning the module version and testing upgrades in a separate AFT deployment (best practices in its AFT IPAM pattern), which is a second AFT environment to keep alive. AWS owns the module code; you own the requests, customizations, state, and VCS connection.
Upgrading the CDK landing zone means bumping two version pins in .projenrc.ts. You run pnpm exec projen, review the diff, and deploy both phases, or push to main and let the pipeline deploy, as Updating Versions describes. Breaking changes and migration steps arrive with the release that needs them. A bump alone doesn't touch the files you own in src/config/ and src/stacks/.
Account Factory for Terraform vs CDK Landing Zone, Side by Side
Each trade-off reduces to one question for your team.
| Trade-off | AFT | CDK landing zone | What decides it for you |
|---|---|---|---|
| IaC ecosystem | Terraform (HCL) | CloudFormation and CDK (TypeScript) | Which IaC does your team already run? |
| Control Tower | Required, plus an AFT management account | Not used; builds on Organizations and StackSets directly | Do you want to depend on Control Tower? |
| Review | HCL; plan-only runs export to S3 or HCP Terraform | TypeScript; StackSet and IAM diff posted on the pull request | Where should reviewers see member-account impact? |
| Vending | Account request file; 5 concurrent account operations by default | Four-line entry; OU-targeted StackSets deploy the baseline | Do you need Control Tower Account Factory records? |
| Day-2 changes | Push, then start aft-invoke-customizations | Merge to main deploys both phases | Is a manual invoke step acceptable for fleet changes? |
| Drift | Control Tower governance drift; no scheduled Terraform drift run | Daily CloudFormation drift detection on landing zone stacks | Should drift on the foundation surface daily? |
| Closing accounts | Removal keeps the account open; unmanage and close by hand | CloseAccountOU closes accounts every 8 hours | Do you close accounts every month? |
| Regions | 10 unavailable; 13 lack third-party VCS for new customers (overlapping) | primaryRegion plus secondaryRegions, at most 15 | Is your home Region on either AFT list? |
| Running cost | About $285/month estimated for the default VPC (us-east-1), plus Control Tower | $59.17/month measured: core, optional security off (9 accounts, August 2026) | Will you run with aft_enable_vpc = false? |
| Upgrades | You apply module upgrades; 3 upgrade-impacting releases since December 2025 | Bump two version pins and redeploy | Who on your team owns foundation upgrades? |
Which One Fits Your Team?
Neither is the better landing zone in general. Each fits a different engineer. AFT fits when most of these hold:
- Your team works in Terraform, and you run Control Tower and plan to keep it, including Account Factory, the controls catalog, and AWS-managed landing zone versions.
- You're on HCP Terraform or Terraform Enterprise, where 1.19.0 added OIDC integration and 1.22.0 added plan export.
- Your code lives in GitLab, Bitbucket, or Azure DevOps.
- Your home Region is on neither of AFT's Region lists.
- You'd rather consume an AWS-maintained module than own the foundation's code.
A CDK landing zone fits when your team works in CloudFormation or CDK, or when you don't want to depend on Control Tower. It builds on Organizations and StackSets directly, so every part of the foundation is code you can read and change. It also fits when you want the organization, SCPs, baselines, and offboarding reviewed in one pull request flow. It assumes your team reviews TypeScript CDK and deploys from a GitHub repository.
If neither fits, CfCT, the Landing Zone Accelerator, and OrgFormation are covered in the five AWS-native alternatives compared.
Frequently Asked Questions
Is AFT worth it for a small AWS organization?
Can AFT run its pipelines in GitHub Actions instead of CodePipeline?
Can a new AFT deployment use CodeCommit?
Can AFT deploy application resources into vended accounts?
Does leaving AFT mean recreating your accounts?
Key Takeaways
- Neither wins outright. AFT is a pipeline on Control Tower for Terraform teams; a CDK landing zone builds on Organizations and StackSets for CloudFormation and CDK teams.
- AFT customizations ship when you invoke them. In the CDK landing zone, the pull request carries the StackSet diff and the merge deploys it.
- Deleting the entry closes nothing on either tool. With AFT you unmanage and close by hand; the CDK landing zone gives you a
CloseAccountOU. - Check your home Region against both AFT Region lists, and budget for the default VPC or set
aft_enable_vpc = false. - You apply every AFT upgrade yourself, including ones that can break an upgrade.
Working in Terraform on Control Tower? Start from AWS's Deploy AFT guide and pin the module version. Working in CloudFormation or CDK, or moving off Control Tower? See how the CDK landing zone runs the account lifecycle, or read the five AWS-native alternatives compared for the wider field.
Next step