The AWS Deployment Framework (ADF) is easy to file under landing zones. It creates accounts, bootstraps organizational units (OUs) and attaches service control policies. But ADF is a multi-account CI/CD framework first, and the account foundation is the part added on top. Weighing it against a CDK landing zone means comparing half of ADF with a whole foundation.
Teams that leaned on the bootstrap folder for their foundation tend to hit this at one of two moments. The v4.0.0 upgrade lists a page of breaking changes, or an auditor asks where the organization trail and GuardDuty setup live.
If you run ADF today, the decision splits in two: what happens to your account foundation, and what happens to your pipelines. I build and maintain the CDK landing zone we deploy for engineering teams, so weigh my bias accordingly.
ADF vs a CDK Landing Zone: The Short Answer
ADF is an Apache-2.0 open-source project on awslabs. Its README describes a framework to "manage and deploy resources across multiple AWS accounts and regions within an AWS Organization," built on CodePipeline, CodeBuild and CodeCommit. Its center of gravity is the deployment map: a YAML file in one deployment account that generates your application pipelines.
A CDK landing zone is the governed foundation itself. The organization, OUs, accounts, SCPs and account baselines are CDK code in your repository. A stack in the management account deploys the organization; CloudFormation StackSets roll out the baselines from a delegated administrator account. The two overlap on three jobs: creating accounts, applying an OU baseline, and managing SCPs.
- Pick ADF if your main need is CodePipeline-based application delivery across accounts from one deployment account, and your foundation is thin or run by AWS Control Tower.
- Pick a CDK landing zone if your
adf-bootstrapfolder has grown into your landing zone and you want accounts, SCPs and baselines reviewed as CDK code. - Already on ADF? The foundation and the pipelines are separate decisions. You can move one and keep the other.
ADF doesn't require Control Tower; if you use both, its installation guide recommends setting Control Tower up first. If Control Tower is on your shortlist, Control Tower's own trade-offs get their own comparison.
What the AWS Deployment Framework Actually Runs
ADF installs into two accounts, and each holds one of its two jobs.
The delivery job lives in the deployment account, which the admin guide calls "the gatekeeper for all deployments throughout an Organization." Teams define pipelines in deployment_map.yml in that account's aws-deployment-framework-pipelines repository. Targets can be OU paths, account IDs or wildcards such as /my_ou/production/**/*, each with its own Regions.
The foundation job lives in the management account, in a CodeCommit repository named aws-deployment-framework-bootstrap. A bootstrap pipeline applies what it holds:
aws-deployment-framework-bootstrap/ # CodeCommit, management account
├── adfconfig.yml
├── adf-accounts/
│ └── payments.yml # account provisioning
└── adf-bootstrap/
├── global.yml # ADF's base, inherited by every OU
├── deployment/
│ └── global.yml
└── workloads/
└── production/
├── global-iam.yml
├── regional.yml
├── scp.json
└── tagging-policy.json
An OU without its own folder inherits its parent's templates. The scp.json and tagging-policy.json files attach an SCP and a tag policy to that OU or account.
Three structural constraints come with it, all in the installation guide:
- The control plane runs in us-east-1 (us-gov-west-1 in GovCloud). An all-Region CloudTrail trail is a prerequisite, because Organizations events can only be acted on in us-east-1.
- The deployment account must sit alone in an OU named
deployment, case-sensitive. The guide says this "cannot be changed currently." - AWS Account Management trusted access has to be enabled by hand.
Excalidraw diagram loading.
All of the overlap sits in that management-account repository.
How Does a New Account Get Its Baseline?
Both tools are OU-driven: put an account in the production OU and it gets the production baseline. What differs is the orchestration. ADF's docs describe stacks deployed through a cross-account role from its own state machine, not StackSets. A CDK landing zone hands the job to CloudFormation StackSets.
Take a new payments account joining the production OU.
In ADF, you add a YAML file to adf-accounts, or Control Tower vends the account. Moving it into the OU records a MoveAccount event, and an EventBridge rule in us-east-1 starts AccountBootstrappingStateMachine in the management account. It assumes the cross-account role (OrganizationAccountAccessRole, or AWSControlTowerExecution with Control Tower) and deploys the OU's bootstrap templates as stacks in each ADF Region. It then adds the account to the deployment account's KMS and S3 policies and starts pipeline generation.
That last step has a timing gap. A deployment map that targets the OU path picks up the account when pipelines regenerate. The pipeline only deploys to it on its next run, unless the pipeline sets restart_execution_on_update: True. If pipeline generation is already running when the move lands, ADF's code skips starting another run, so you may need to release a change by hand.
In the CDK landing zone, the account is an entry in organization-structure.ts:
ProductionOU: {
name: 'workload-prod-ou',
accounts: {
PaymentsAccount: {
name: 'Payments',
email: 'aws+payments-prod@example.com',
},
},
},
The pull request runs pnpm run organization:diff, which shows one new AWS::Organizations::Account resource in LandingZoneOrganizationStack. The merge deploys the organization phase, which creates the account inside the OU. You don't re-run the landing-zone phase. The baseline StackSets are service-managed and target the OU, so automatic deployment adds their stack instances as soon as the account joins.
The difference is who operates the fan-out. With ADF, the state machine and its Organizations API calls are yours to troubleshoot; with StackSets, it's a CloudFormation feature. The Add an Account guide ends by checking each StackSet instance reached CURRENT.
What's in the Baseline on Day One?
ADF's baseline is whatever you write. The installer's first commit holds "a default set of examples," and from there you define your base stacks as CloudFormation templates, SCPs and tag policies. Bootstrap templates accept any CloudFormation, but out of the box ADF doesn't provide an organization trail, GuardDuty, Security Hub, Inspector or Macie enablement, delegated administrators, budgets, alternate contacts, drift detection or declarative policies.
A quick audit: count what in your adf-bootstrap folder isn't one of ADF's examples. If that list includes logging, security services and guardrails, the folder is already your landing zone, kept as hand-written CloudFormation.
The CDK landing zone ships that content. It rolls out 8 StackSets and attaches 5 SCPs, composed from 28 constructs:
- An organization trail in a dedicated observability account, which also emails the organization's alerts
- GuardDuty, Security Hub, Inspector and Macie configured organization-wide from a delegated security account
- Secure account defaults and a Resource Explorer index in member accounts
- CDK bootstrap and service quota requests for the Development and Production OUs
- Alternate contacts, a FinOps account, and optional cost budgets per OU or account
- Daily CloudFormation drift detection in the management and landing zone accounts
SCPs: One File per Folder vs a Policy List per OU
ADF attaches one scp.json per OU or account folder. An open feature request, #724, asks to deploy multiple SCPs to a single OU or account. Until it lands, every guardrail for an OU lives in one policy document.
Removing a file has a side effect the docs don't describe. In ADF's organization_policy.py, the next bootstrap run detaches and deletes that policy, and re-attaches FullAWSAccess to the target. So when you migrate, attach the replacement policy first and delete the file second.
The CDK landing zone takes a policies array on any OU or account. It holds service control policies, declarative policies and tag policies, up to 10 of each type per target. This is the list at the organization root:
policies: [
criticalSecurityGuardrailsSCP,
protectTaggedCloudFormationStacksSCP,
denyAllOutsidePrimaryAndSecondaryRegionsSCP,
blockPublicEc2SharingDeclarativePolicy,
],
Where Does a Foundation Change Get Reviewed?
In ADF, the review is the pull request. The admin guide says changes to adfconfig.yml or any bootstrap template "should be done via a Pull Request" into the management-account repository. A merge starts the bootstrap pipeline, which updates bootstrap templates on accounts throughout the organization.
What the reviewer sees is the template diff. The bootstrap pipeline as shipped has no approval stage, and the CloudFormation change sets ADF creates internally execute straight away. SCP edits go to the Organizations API directly. The reviewer has to predict each account's outcome from the YAML. Deployment-map pipelines are different: their CloudFormation stages in the pipeline's main Region accept change_set_approval: true, which pauses between creating and executing the change set.
In the CDK landing zone, the pull request carries the effect as well as the code. Three generated GitHub Actions workflows cover the change:
- Offline validation runs the Jest suite, then checks every synthesized template against CDK's bundled CloudFormation rules.
- The diff workflow runs
organization:diffandlandingzone:diff:alland posts one combined comment. It lists the resources and IAM statements each StackSet changes in your member accounts, not only a new template URL on the parent stack. - On merge to
main, the deploy workflow runs both phases through GitHub Actions with OpenID Connect (OIDC), so no long-lived AWS keys are stored anywhere.
Push again and the comment is rewritten in place, never duplicated. Pull requests from forks are skipped, because a fork shouldn't hold a role that can read your organization. The GitHub Actions deployment guide covers the setup. After the deploy, daily drift detection catches out-of-band changes to the landing zone's own stacks and StackSets.
Upgrading ADF: v4.0.0 and the Unreleased Commits
ADF's latest release is v4.0.0, published on 11 June 2024. Commits have landed on master since that tag, including bug fixes and dependency updates, but no newer release packages them. Install from the v4.0.0 tag and you get none of them unless you deploy from master.
An upgrade means re-running make and make deploy. ADF then opens a pull request against the bootstrap repository's main branch, and you merge it along with any custom pipeline types and bootstrap edits. Don't edit adf-bootstrap/global.yml: ADF overwrites it on upgrade, which is why customizations belong in global-iam.yml.
The v4.0.0 CHANGELOG calls it a security-focused release and asks you to test in a non-production environment first:
| v4.0.0 change | What you check before upgrading |
|---|---|
aws:SourceArn confused-deputy conditions on ADF roles | Anything outside ADF that uses ADF roles |
| New jump role; account management limited to non-protected OUs | Which OUs you list as protected |
Management account bootstrapped only if AllowBootstrappingOfManagementAccount=Yes | Anything ADF deploys into the management account |
| KMS encryption required on deployment account pipeline buckets | Files that non-ADF systems copy into them |
| CodeStar Connections become CodeConnections; OAuth tokens dropped | GitHub and Bitbucket sources in deployment maps |
| CodeBuild default image removed | Every pipeline names its build image |
CDK v1 to v2; SAR installer replaced by make deploy | Custom pipeline types and your install runbook |
At scale, #750 reports that bootstrapping 400+ accounts fails with v4.0.0. Four open issues report Organizations API throttling during bootstrap, including #801. These are reports, not confirmed root causes.
ADF's pipeline construct never sets PipelineType, so its pipelines default to V1. V1 lacks Git tag, pull request and file-path triggers, QUEUED and PARALLEL execution modes, and stage rollback.
On the CDK landing zone, an upgrade is two version pins in .projenrc.ts. Run pnpm exec projen, review the diff, and deploy; the pull request comment previews the effect like any other change.
Keep ADF for App Pipelines and Move the Foundation?
Yes, and ADF's installation guide describes the pattern. It splits the work with Control Tower: Control Tower creates accounts and maps OUs, while ADF deploys applications from deployment maps. A foundation in code can take Control Tower's place in that split.
The catch is that ADF pipelines need ADF's bootstrap in every account they deploy to. Deployment actions assume adf-cloudformation-role in the target, which passes adf-cloudformation-deployment-role to CloudFormation. Both roles come from adf-bootstrap/global.yml. The deployment account's KMS and S3 policies are also updated per bootstrapped account. Keeping ADF for delivery means keeping its bootstrap on the workload OUs it targets. Protected OUs skip bootstrap, so they don't help here.
A clean split looks like this:
- The landing zone owns accounts, OUs, SCPs, tag policies, logging, security services and budgets. ADF's docs treat account provisioning as optional when Control Tower vends accounts; the same holds when the landing zone does.
- ADF keeps
global.yml, its regional stacks and the deployment account, the minimum its pipelines need. - Each OU gets one SCP owner. ADF's policy code doesn't consult the protected list, so an
scp.jsonin a protected OU's folder still applies. - ADF's Regions stay inside the landing zone's. The root Region guardrail doesn't exempt ADF's roles, so
DeploymentAccountMainRegionand every target Region must be inprimaryRegionorsecondaryRegions.
The other route moves delivery too. The landing zone's CdkBootstrapStackSet mirrors the default cdk bootstrap (qualifier hnb659fds) in every Development and Production OU account, and creates the GitHub Actions OIDC provider there. Each workload repository adds its own GitHub OIDC deploy role and runs its own pipeline. CDK Pipelines is the AWS-native option if you want a self-mutating one. The trade-off is one gatekeeper deployment account against a deploy role per repository.
Before the StackSet reaches an existing account, check for a CDKToolkit stack. It collides on the fixed cdk-hnb659fds-* names. In an account with no deployed CDK apps, delete it and its assets bucket, and empty its ECR repository, first. Where CDK apps are live, plan that cutover separately: AWS warns against deleting and recreating a bootstrap stack, and running services may still pull images from that repository. The StackSets reference lists every resource, and what cdk bootstrap creates explains each one.
AWS Deployment Framework vs CDK Landing Zone, Side by Side
| Trade-off | ADF | CDK landing zone | What decides it for you |
|---|---|---|---|
| Primary job | Multi-account CI/CD from deployment maps | The account foundation | Whether delivery or governance is your gap |
| Baseline mechanism | Step Functions deploys stacks through a cross-account role | Service-managed StackSets auto-deploy to the OU | Who you want operating the rollout |
| Day-one baseline | Examples; you write the rest | 8 StackSets, 5 SCPs, 28 constructs | How much baseline you've already written |
| SCPs per target | One scp.json per folder | policies array, up to 10 per type | How many guardrails each OU needs |
| Review before apply | Pull request on the template diff | Comment with stack and StackSet diffs | Who must see the effect before merge |
| Control plane | us-east-1 or us-gov-west-1 | Your primary Region | Where your home Region is |
| Pipelines | One deployment account; CodePipeline V1 by default | Workload accounts bootstrapped for per-repo pipelines | Central gatekeeper or per-team pipelines |
| Upgrades | Re-run make deploy, merge the PR it opens | Bump two version pins, review the diff, deploy | How much customization you merge per upgrade |
| Control Tower | Compatible; if used, set it up first | Not used | Whether you want Control Tower at all |
Which One Fits Your Team?
ADF fits when application delivery through CodePipeline is the main job, and one deployment account as gatekeeper suits how you work. It fits best when the foundation is thin or Control Tower runs it. Your team should be comfortable writing baseline CloudFormation and merging customizations on every upgrade.
A CDK landing zone fits when the bootstrap folder has become the landing zone. It suits teams that want the organization, SCPs, baselines and offboarding in one pull request flow, with each change's effect visible before merge. It assumes your team reviews TypeScript CDK and deploys from a GitHub repository.
If neither fits, compare other AWS-native landing zone options. Terraform teams on Control Tower should see how AFT works as a pipeline on top of Control Tower.
Frequently Asked Questions
What does the AWS Deployment Framework cost to run?
Can a new ADF installation still use CodeCommit?
Does ADF only work in us-east-1?
Can ADF pipelines pull from GitHub or GitLab?
Does leaving ADF mean recreating our accounts?
Key Takeaways
- ADF does two jobs. A CDK landing zone replaces the foundation job and leaves delivery to you.
- Both are OU-driven. ADF orchestrates with its own state machine and cross-account stacks; the CDK landing zone uses StackSets automatic deployment.
- ADF's baseline is yours to write; the CDK landing zone ships one.
- v4.0.0 from June 2024 is still ADF's latest release, and later fixes on master are unreleased.
- ADF pipelines need ADF's bootstrap in their target accounts, so keeping them means keeping that layer on workload OUs.
Your next step: list what in adf-bootstrap is foundation and what in deployment_map.yml is delivery. The first list is the AWS Deployment Framework vs CDK landing zone decision; the second can stay in ADF or move to per-repository pipelines. For the wider field, read the five AWS-native alternatives compared.
Next step