AWS CDK Landing Zone

Install

Set up the AWS CDK Landing Zone from first clone to a fully deployed multi-account AWS environment.

Prerequisites

Before you start, make sure you have:

  • Node.js 24.18.0 or later: the project pins its Node version; check with node --version
  • pnpm: install via corepack enable (recommended) or brew install pnpm
  • AWS CDK CLI: npm install -g aws-cdk
  • projen: already included as a dev dependency, no separate install needed. projen is the project-generation tool that owns your configuration files: you edit .projenrc.ts and run pnpm exec projen to regenerate package.json, the organization:* and landingzone:* npm scripts, and the GitHub Actions workflow. Never hand-edit generated files; change .projenrc.ts instead. See the projen reference for how it works
  • An AWS management account: the root account of your AWS Organization. If you don't have an organization yet, the landing zone creates one for you in the organization phase
  • A landing zone account: a dedicated member account that deploys the StackSets as the CloudFormation StackSets delegated administrator. The organization phase creates this LandingZoneAccount for you, so you do not need it up front; you fill its ID into settings between the two phases
  • GitHub repository: the starter expects a GitHub remote; the OIDC trust policy is scoped to your repository

Step 1: Create your repository from the template

Click Use this template on the cdk-landing-zone-starter GitHub repository to create your own private copy, then clone it:

git clone git@github.com:<your-org>/<your-repo>.git
cd <your-repo>

Step 2: Get your management account whitelisted

Share your management account ID with Towards The Cloud. We whitelist it on the private CodeArtifact registry that hosts @towardsthecloud/cdk-landing-zone-constructs and @towardsthecloud/cdk-landing-zone-foundation. Until your account is whitelisted, pnpm install cannot resolve these packages.

Don't have access yet? Book a demo and we'll walk you through the landing zone and get you set up.

Step 3: Install dependencies

Assume a role or configure credentials in your whitelisted management account, then run:

pnpm run setup

This logs in to CodeArtifact and runs pnpm install --frozen-lockfile in one step. The login script (scripts/codeartifact-login.sh) fetches an auth token and writes it as a registry-scoped _authToken in your user-level ~/.npmrc. It does not change your default npm registry. Tokens are valid until your AWS session expires. If pnpm install returns a 401, re-assume your role and run pnpm run setup again.

Step 4: Configure landing zone settings

Open src/config/landing-zone-settings.ts and replace every placeholder value:

export const landingZoneSettings: LandingZoneSettings = {
  organizationName: 'your-org', // short slug, lowercase and dashes
  mailDomain: 'your-org.com', // domain for generated account email addresses
  managementAccountId: '123456789012', // 12-digit management account ID
  // landingZoneAccountId: '234567890123', // fill in after the organization phase (see Step 9)
  primaryRegion: 'eu-west-1', // main deployment region
  secondaryRegions: [], // additional regions for multi-region StackSets
};

Leave landingZoneAccountId commented out for now. The organization phase creates the LandingZoneAccount and publishes its ID; you fill it in before the landing-zone phase in Step 9. See Landing Zone Settings for the full field reference.

Optional settings

Only add these fields when you explicitly need the extra behavior:

export const landingZoneSettings: LandingZoneSettings = {
  // ...required settings above
  externalAdmins: [
    { accountId: '111122223333', managedPolicyName: 'ReadOnlyAccess' },
  ], // grants third-party access through the optional ExternalAccessStackSet
  organizationAccountLimit: 120, // requests an AWS Organizations account quota increase
};

Use externalAdmins when a trusted third-party account needs cross-account access. Use organizationAccountLimit only when your planned account count exceeds the current AWS Organizations quota.

Step 5: Configure your organization structure

Open src/config/organization-structure.ts and replace the example Workload Alpha/Workload Beta accounts with your own OUs and accounts. Every account needs a unique, valid email address.

See Organization Structure for the full field reference and constraints.

Step 6: Configure alternate contacts

Open src/config/alternate-contacts.ts and set the real security, billing, and operations contacts for your organization. These are applied to every account.

See Alternate Contacts for the field reference.

Step 7: Regenerate project files

Run projen to embed your management account ID and primary region into the generated npm scripts and GitHub Actions workflow:

pnpm exec projen

Re-run this command whenever you change landing-zone-settings.ts.

Step 8: Bootstrap the CDK toolkit

cdk bootstrap aws://123456789012/eu-west-1

Replace the account ID and region with your management account values.

Step 9: First deployment (two phases)

The initial rollout is split across two AWS accounts. The organization phase runs against your management account and creates the organization, including the dedicated landing zone account. The landing-zone phase runs against that landing zone account and deploys the StackSets. The organization must exist, and the landing zone account must be registered as the StackSets delegated administrator, before the landing-zone StackSets can target the organization's accounts.

Organization phase: deploy the organization stack

With credentials for your management account, run:

pnpm run organization:deploy

This creates your AWS Organization, OUs, and accounts (including the LandingZoneAccount), and publishes their IDs as CloudFormation stack outputs on LandingZoneOrganizationStack. It also registers the delegated administrators from delegated-admins.ts, bootstraps the landing zone account (CDK toolkit, asset buckets, and a landingzone GitHub Actions deploy role), and sets up the OIDC provider and GitHubActionsServiceRole in your management account, scoped to this repository. Once it completes, the GitHub Actions pipeline is authorized to deploy without any manual IAM setup.

Only OU IDs and the account IDs of accounts marked publishAccountIdOutput: true in organization-structure.ts are published as outputs.

Set the landing zone account ID

The organization phase publishes the new account's ID as the LandingZoneAccountId output. Read it, for example:

aws cloudformation describe-stacks --stack-name LandingZoneOrganizationStack \
  --query "Stacks[0].Outputs[?OutputKey=='LandingZoneAccountId'].OutputValue" --output text

Set it in src/config/landing-zone-settings.ts (uncomment landingZoneAccountId and paste the value), then re-run projen so the generated landingzone:* scripts and workflow target the right account:

pnpm exec projen

Landing-zone phase: deploy the landing zone stacks

The landing-zone stacks deploy from the landing zone account, so switch to credentials for that account (assume a role in it), then run:

pnpm run landingzone:deploy:all

This deploys LandingZoneFoundationStack and LandingZoneAccountProvisioningStack. They read the organization IDs from the LandingZoneOrganizationStack outputs cross-account at deploy time, so there is nothing to cache between the two phases.

Or let GitHub Actions run both phases. Commit your configuration and push to main (or trigger the workflow manually). The pipeline assumes the management-account role for the organization phase, then the landing-zone-account role for the landing-zone phase, in one run:

git add -A
git commit -m "chore: configure landing zone"
git push

Ongoing deployments

After the initial rollout, every push to main triggers the GitHub Actions pipeline, which runs the organization phase against the management account and then the landing-zone phase against the landing zone account. To deploy locally, run pnpm run organization:deploy with management-account credentials and pnpm run landingzone:deploy:all with landing-zone-account credentials.

To update the landing zone packages, see Updating Versions.