This repository bootstraps an AWS Organization and deploys self-mutating CDK pipelines from a Shared Services account. The intended source control model is GitHub via AWS CodeConnections.
The repository currently supports:
- organization setup from
scripts/org/org-config.yaml - one infrastructure pipeline in the Shared Services account
- one application pipeline per configured app
- explicit CodePipeline V2 triggers with branch filters and optional path include/exclude filters
- optional centralized security resources and optional centralized egress networking
- Local developer commands are
npm-first and come frompackage.json. - Pipeline synth in both pipeline stacks installs
pnpmand runspnpm install --frozen-lockfile,pnpm run build, andpnpm -r run build. - The repo currently has
pnpm-workspace.yamlbut no checked-inpnpm-lock.yaml. Before deploying the pipelines, generate and commit a lockfile or the pipeline synth step will fail. - The default
scripts/org/org-config.yamlships withapps: []. config/load-config.tsrequires at least one app, sonpm run synthandcdk synthwill not work until you add an app and regenerateconfig/defaults.ts.- The checked-in sample app is
lib/testapp. It creates an S3 bucket and can optionally deploy spoke networking whenspokeCidris present.
Pipelines are sourced from GitHub through AWS CodeConnections, not CodeCommit.
Both lib/pipeline/infra-pipeline-stack.ts and lib/pipeline/app-pipeline-stack.ts:
- create
CodePipelineSource.connection(...)sources - set
pipelineType: codepipeline.PipelineType.V2 - disable default source push detection with
triggerOnPush: false - add an explicit V2 trigger with
repositoryBranch - optionally apply
triggerOnPaths.filePathsIncludes - optionally apply
triggerOnPaths.filePathsExcludes
triggerOnPaths is validated in config/load-config.ts:
- maximum 8 include patterns
- maximum 8 exclude patterns
- Node.js
>=20 <23 - AWS CLI configured for the management account
jqavailable locally if you plan to usescripts/act-as-account.sh
Install dependencies:
npm installEdit scripts/org/org-config.yaml.
This file is the source of truth for:
- AWS region
- IAM Identity Center bootstrap identity
- OU and account layout
- Shared Services account designation
- pipeline source settings
- optional centralized egress network settings
By default it creates or reuses only these member accounts:
- Shared Services
- Log Archive
- Audit
Workload accounts are intentionally left commented out for the first 24 hours after organization creation.
aws organizations create-organization --feature-set ALL
aws ram enable-sharing-with-aws-organization
npm run org:build
npm run org:moveWhat these scripts do:
org:buildcreates or reuses OUs and accounts, and records state in.org-state.jsonorg:movewaits for account creation to finish, recovers reusable accounts where possible, and moves each account into its target OU
npm run org:setup-principalThis creates a management-account setup role and temporary IAM user so the remaining cross-account scripts can run without using the root user.
After following the printed credential/profile instructions, optional console setup can continue with:
npm run org:iamnpm run org:scps
npm run org:securityCurrent behavior:
org:scpsdeploys preventive SCPs from the management accountorg:securityenables default EBS encryption, S3 Block Public Access, organization-wide IAM Access Analyzer, and registers the Audit account as the CloudTrail delegated administrator
npm run org:bootstrapTo skip accounts that were detected as pre-existing during org:build:
npm run org:bootstrap -- --skip-existing-accountsThe repository is not synthesizable until at least one app exists.
Use the helper:
npm run org:add-app -- --name myapp --repoName my-repoWhat org:add-app currently does:
- appends app pipeline config to
scripts/org/org-config.yaml - adds workload account entries under discovered stages
- scaffolds
lib/<app>/app-stage.ts - scaffolds
lib/<app>/stacks/app-resources-stack.ts - adds path filters for the new app pipeline and infra pipeline
What it does not do:
- it does not scaffold
packages/<app>/ - it does not add runtime application code
After adding the app, create and place the workload accounts:
npm run org:build
npm run org:move
npm run org:bootstrap
npm run org:finalizenpm run org:finalizescripts/org/finalize.ts currently:
- resolves live account IDs from AWS Organizations
- writes
config/defaults.ts - renames
README.mdtoSETUP.mdifSETUP.mddoes not already exist - recreates an empty
README.md
That README rewrite behavior is real current code, even though it is surprising for a template repository.
Create an AWS CodeConnections connection in the Shared Services account and in the same region as the pipeline stack.
Then set the connection ARN in scripts/org/org-config.yaml:
infraPipeline.connectionArnapps[].pipeline.connectionArn
The connection must be in the Shared Services account because that is where the pipeline stacks are deployed.
Use the current helper script:
source ./scripts/act-as-account.sh <shared-services-account-id>Then deploy:
npm run synth
npm run deploy -- '*Pipeline'After that, pushing to the configured GitHub branch triggers the pipelines through CodeConnections.
The repo is designed for monorepo-style path filtering, but the filters are optional.
Recommended app include set:
triggerOnPaths:
filePathsIncludes:
- "lib/<app>/**"
- "packages/<app>/**"
- "lib/constructs/**"
- "lib/infra/spoke-network-stack.ts"
- "lib/pipeline/app-pipeline-stack.ts"
- "lib/app-stage-props.ts"
- "bin/app.ts"
- "config/**"Recommended infra excludes:
infraPipeline:
triggerOnPaths:
filePathsExcludes:
- "packages/**"
- "lib/<app>/**"Notes:
- the include pattern
packages/<app>/**is useful if you later add runtime code there - the template checkout does not currently include populated app packages
- branch filters always apply even when path filters are omitted
Networking is controlled from scripts/org/org-config.yaml, not from config/local.ts.
The code expects:
network:
egressVpcCidr: "10.0.0.0/16"
spokeCidrs:
dev: "10.1.0.0/16"
prod: "10.2.0.0/16"
tgwAsn: 64512Then run:
npm run org:finalizeCurrent implementation:
- Shared Services can deploy a Transit Gateway, egress VPC, RAM share, SSM parameters, and a cross-account SSM reader role
- workload stages with
spokeCidrdeploy a private spoke VPC, TGW attachment, SSM endpoints, and a demo EC2 instance
Relevant files:
Security account IDs are generated into config/defaults.ts by org:finalize when Log Archive and Audit accounts exist.
The account layout follows the AWS Security Reference Architecture (SRA):
- Log Archive account — immutable log storage (CloudTrail, Config)
- Audit account — delegated administrator for security services (CloudTrail, and eventually GuardDuty, SecurityHub, Config); also hosts budget/cost anomaly alerts
- Shared Services account — CI/CD pipelines and shared operational infrastructure only
Optional security overrides such as alert email or allowed regions belong in config/local.ts, for example:
import type { BootstrapConfig } from "./schema";
export const localConfig: Partial<BootstrapConfig> = {
security: {
alertEmail: "security@example.com",
monthlyBudgetUsd: 1000,
allowedRegions: ["us-east-1"],
},
};Current implementation includes:
- log archive S3 buckets deployed to the Log Archive account
- org-wide CloudTrail trail deployed to the Audit account (currently disabled pending management account CDK bootstrap)
- budget alerts and cost anomaly detection deployed to the Audit account
- org scripts for SCP deployment, account-level security defaults, and Audit account delegated admin registration
Relevant files:
- lib/infra/security-stage.ts
- lib/infra/log-archive-stack.ts
- lib/infra/org-cloudtrail-stack.ts
- lib/infra/budget-alerts-stack.ts
The codebase includes domain-related support, but it is not a built-in end-to-end feature of the checked-in sample app.
Current state:
lib/infra/shared-services-stage.tsdeploys shared Route 53 resources when an app hasdomainconfig- the checked-in sample app in
lib/testappdoes not use domain config - the repo includes reusable constructs such as
lib/constructs/media-cdn.tsandlib/constructs/ses-email-identity.tsfor extension work
This README does not treat GraphQL, Stripe, Cognito, SES, or CloudFront constructs as turnkey template features because they are not wired into a complete default application flow in the current repository.
Commands from package.json:
| Command | Description |
|---|---|
npm install |
Install dependencies and run Husky setup |
npm run build |
Compile TypeScript to dist/ |
npm test |
Build and run the Node.js test suite from dist/test/*.test.js |
npm run check |
Run Biome checks |
npm run lint |
Run Biome lint checks |
npm run lint:fix |
Apply Biome lint fixes |
npm run format |
Apply Biome formatting |
npm run format:check |
Check Biome formatting |
npm run synth |
Run cdk synth |
npm run deploy |
Run cdk deploy |
npm run bootstrap:commands |
Print bootstrap commands from resolved config |
npm run org:add-app |
Add a new app to org config and scaffold lib/<app>/ |
npm run org:setup-principal |
Create the management-account setup principal |
npm run org:build |
Create or reuse OUs and accounts |
npm run org:move |
Move accounts into their target OUs |
npm run org:bootstrap |
Bootstrap Shared Services and member accounts for CDK |
npm run org:iam |
Create the IAM Identity Center user/group/assignment |
npm run org:scps |
Deploy preventive SCPs |
npm run org:security |
Enable EBS encryption, S3 BPA, IAM Access Analyzer, and register Audit as CloudTrail delegated admin |
npm run org:finalize |
Generate config/defaults.ts from live AWS data |
bin/
app.ts
config/
schema.ts
defaults.ts
local.example.ts
load-config.ts
lib/
app-stage-props.ts
constructs/
infra/
pipeline/
testapp/
scripts/
act-as-account.sh
bootstrap/
org/
test/
*.test.ts
pnpm-workspace.yaml
package.json
Notes:
lib/testapp/is the only checked-in sample app.packages/is part of the intended workspace shape, but it is not populated in this checkout.- pipeline code dynamically loads
lib/<app>/app-stage.tsfor each configured app.