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) orbrew 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.tsand runpnpm exec projento regeneratepackage.json, theorganization:*andlandingzone:*npm scripts, and the GitHub Actions workflow. Never hand-edit generated files; change.projenrc.tsinstead. 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
LandingZoneAccountfor 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.