Overview
The starter kit ships with GitHub Actions workflows generated by Projen so every merge to your default branch produces a deterministic deploy. This guide explains the lifecycle from merge to rollout and how to interact with the pipelines.
All workflows are automatically generated from the configuration in .projenrc.ts by the createCdkDeploymentWorkflows and createCdkDiffPrWorkflow helper functions. See the Reference: Helpers page for implementation details.
Prerequisites
Before the workflows can function properly, you need to configure GitHub settings and secrets:
1. Create PROJEN_GITHUB_TOKEN
The build.yml and auto-approve.yml workflows require a Personal Access Token (PAT) to push commits and enable auto-merge.
Steps to create the token:
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click Generate new token (classic)
- Configure the token:
- Note:
PROJEN_GITHUB_TOKEN for AWS CDK Starter Kit - Expiration: Choose your preferred expiration (recommendation: 90 days or 1 year)
- Scopes: Select:
- ✅
repo(Full control of private repositories) - ✅
workflow(Update GitHub Action workflows)
- ✅
- Note:
- Click Generate token and copy the token value
Add the token to your repository:
- Go to your repository → Settings → Secrets and variables → Actions
- Click New repository secret
- Configure the secret:
- Name:
PROJEN_GITHUB_TOKEN - Secret: Paste the token value you copied
- Name:
- Click Add secret
2. Enable GitHub Actions permissions
The auto-approve.yml workflow needs permission to create and approve pull requests.
Steps to enable:
- Go to your repository → Settings → Actions → General
- Scroll to Workflow permissions
- Enable: ✅ Allow GitHub Actions to create and approve pull requests
- Click Save
Direct link for your repository:
https://github.com/<your-org>/<your-repo>/settings/actions
:::info Without these settings, the build workflow's self-mutation job will fail to push commits, and the auto-approve workflow won't be able to enable auto-merge for Dependabot PRs. :::
Trigger flow
- Push or merge into
main: Projen creates a workflow namedcdk-deploy-<env>for each environment. The first environment inenvironmentConfigs(typicallytest) runs on every push tomain. - Environment chaining: Subsequent environments trigger via
workflow_runwhen the previous deploy finishes successfully. For example,cdk-deploy-productionwaits forcdk-deploy-testto succeed before starting. - Pull requests: Branch deploy workflows named
cdk-deploy-<env>-branchrun whenenableBranchDeployis true for that environment. They deploy stacks with branch suffixes so previews stay isolated. Cleanup is handled bycdk-destroy-<env>-branchon branch deletion or PR close. - CDK diff on pull requests: The
cdk-diff-pr-commentworkflow runs automatically on every pull request targetingmain. It generates a CDK diff comparing your PR branch against the production environment and posts the results as a comment directly in the pull request. This gives reviewers visibility into infrastructure changes before merging. - Manual runs: Every workflow exposes the
workflow_dispatchtrigger, enabling re-runs or ad-hoc deploys straight from the GitHub Actions UI.
Workflow configurations
All workflows in .github/workflows/ are generated by Projen based on your environment configuration. The sections below explain the default setup for each workflow type.
Environment deployment workflow
File: .github/workflows/cdk-deploy-<env>.yml (e.g., cdk-deploy-test.yml)
Purpose: Deploys CDK stacks to a specific environment on push to main or when the previous environment completes successfully.
Example configuration for test environment:
name: cdk-deploy-test
on:
workflow_dispatch: {} # Manual trigger from GitHub UI
push:
branches:
- main # First environment triggers on push to main
# OR for subsequent environments:
# workflow_run:
# workflows: [cdk-deploy-test]
# types: [completed] # Waits for previous environment to complete
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: false # Prevents concurrent deployments
jobs:
deploy:
name: Deploy CDK stacks to test AWS account
runs-on: ubuntu-latest
permissions:
contents: read # Read repository contents
id-token: write # Required for OIDC authentication
environment: test # GitHub environment for protection rules
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup nodejs environment
uses: actions/setup-node@v6
with:
node-version: ">=22.18.0"
cache: npm
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::987654321012:role/GitHubActionsServiceRole
aws-region: us-east-1
- name: Install dependencies
run: npm ci
- name: Run CDK synth for the TEST environment
run: npm run test:synth
- name: Deploy CDK to the TEST environment on AWS account 987654321012
run: npm run test:deploy:all
Key configuration details:
- Trigger: First environment (test) triggers on push to
main. Subsequent environments useworkflow_runto chain after the previous environment completes. - Concurrency: Prevents multiple deployments to the same environment simultaneously with
cancel-in-progress: false. - Permissions: Minimal permissions—
contents: readfor checkout andid-token: writefor OIDC authentication. - Environment: Uses GitHub environments for protection rules and deployment tracking.
- OIDC Role: Assumes the
GitHubActionsServiceRolecreated by the FoundationStack for secure, keyless authentication. - Tasks: Generated by
addCdkActionTaskin.projenrc.ts:165. Thetest:synthandtest:deploy:allcommands are created with environment-specific variables.
Generation: Created by createCdkDeploymentWorkflow in src/bin/cicd-helper.ts:146-217.
Branch deployment workflow
File: .github/workflows/cdk-deploy-<env>-branch.yml (e.g., cdk-deploy-test-branch.yml)
Purpose: Deploys feature branch stacks with branch-suffixed names for isolated testing.
Example configuration:
name: cdk-deploy-test-branch
on:
workflow_dispatch: {}
push:
branches:
- "**" # All branches
- "!main" # Except main
- "!hotfix/*" # Except hotfix branches
- "!github-actions/*" # Except GitHub Actions branches
- "!dependabot/**" # Except Dependabot branches
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: false
jobs:
deploy:
name: Deploy CDK stacks to test AWS account (Branch)
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment: test
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup nodejs environment
uses: actions/setup-node@v6
with:
node-version: ">=22.18.0"
cache: npm
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::987654321012:role/GitHubActionsServiceRole
aws-region: us-east-1
- name: Install dependencies
run: npm ci
- name: Run CDK synth for the TEST environment
run: npm run test:branch:synth
- name: Deploy CDK to the TEST environment on AWS account 987654321012
run: npm run test:branch:deploy:all
Key configuration details:
- Trigger: Runs on push to any branch except
main,hotfix/*,github-actions/*, anddependabot/**. - Branch tasks: Uses
test:branch:synthandtest:branch:deploy:alltasks that include theGIT_BRANCH_REFenvironment variable. - Stack naming: The
GIT_BRANCH_REFvariable is processed byextractCleanedBranchNameto create a sanitized suffix (lowercase, max 25 chars, alphanumeric + hyphens). - Resource naming: Stack names are generated by
createEnvResourceNamewith the branch suffix (e.g.,MyStack-feature123).
Generation: Created by createCdkDeploymentWorkflow in src/bin/cicd-helper.ts:146-217 when deployForBranch=true.
Branch destroy workflow
File: .github/workflows/cdk-destroy-<env>-branch.yml (e.g., cdk-destroy-test-branch.yml)
Purpose: Removes branch deployment stacks when branches are deleted, PRs are closed, or manually triggered.
Example configuration:
name: cdk-destroy-test-branch
on:
workflow_dispatch: {} # Manual cleanup
delete:
branches:
- "**" # All branches
- "!main" # Except main
- "!hotfix/*" # Except hotfix branches
- "!github-actions/*" # Except GitHub Actions branches
- "!dependabot/**" # Except Dependabot branches
jobs:
destroy:
name: Remove deployment of feature branch
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment: test
if: github.head_ref != 'main' || (github.event.ref_type == 'branch' && github.event_name == 'delete') || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup nodejs environment
uses: actions/setup-node@v6
with:
node-version: ">=22.18.0"
cache: npm
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::987654321012:role/GitHubActionsServiceRole
aws-region: us-east-1
- name: Install dependencies
run: npm ci
- name: Fetch Deleted Branch Name
id: destroy-branch
if: github.event.ref_type == 'branch' && github.event_name == 'delete'
run: BRANCH=$(cat ${{ github.event_path }} | jq --raw-output '.ref'); echo "${{ github.repository }} has ${BRANCH} branch"; echo "DESTROY_BRANCH_NAME=$BRANCH" >> $GITHUB_OUTPUT
- name: Destroy Branch Stack (Workflow Dispatch)
if: github.event_name == 'workflow_dispatch'
env:
GIT_BRANCH_REF: ${{ github.ref_name }}
run: npm run test:branch:destroy:all
- name: Destroy Branch Stack (Branch Deletion)
if: github.event.ref_type == 'branch' && github.event_name == 'delete'
env:
GIT_BRANCH_REF: ${{ steps.destroy-branch.outputs.DESTROY_BRANCH_NAME }}
run: npm run test:branch:destroy:all
- name: Destroy Branch Stack (Pull Request Closure)
if: github.event_name == 'pull_request'
env:
GIT_BRANCH_REF: ${{ github.head_ref }}
run: npm run test:branch:destroy:all
Key configuration details:
- Triggers: Three scenarios—manual dispatch, branch deletion, or PR closure.
- Branch extraction: For delete events, uses
jqto extract the branch name from the GitHub event payload. - Conditional steps: Each destroy step runs conditionally based on the trigger event type.
- Safety check: Job-level
ifcondition prevents accidental main branch destruction.
Generation: Created by createCdkDestroyWorkflow in src/bin/cicd-helper.ts:235-303.
CDK diff PR comment workflow
File: .github/workflows/cdk-diff-pr-comment.yml
Purpose: Generates and posts CDK diff output as a PR comment for infrastructure change visibility.
Configuration:
name: cdk-diff-pr-comment
on:
pull_request_target: # Allows access to secrets while checking out PR code
branches:
- main
jobs:
deploy:
name: CDK diff PR branch with production environment (via main)
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
pull-requests: write # Required to post comments
env:
AWS_REGION: us-east-1
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }} # Checkout PR branch
- name: Setup nodejs environment
uses: actions/setup-node@v6
with:
node-version: ">=22.18.0"
cache: npm
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsServiceRole
aws-region: us-east-1
- name: Install dependencies
run: npm ci
- name: CDK diff and notify PR
run: npm run production:diff:all > cdk-diff-output.txt 2>&1 || true
- name: Post CDK Diff Comment in PR
uses: towardsthecloud/aws-cdk-diff-pr-commenter@v1
with:
diff-file: cdk-diff-output.txt
aws-region: us-east-1
header: CDK Diff for production in us-east-1
Key configuration details:
- Trigger: Uses
pull_request_targetfor secure access to repository secrets while checking out PR code. - PR checkout: Explicitly checks out the PR branch SHA using
github.event.pull_request.head.sha. - Diff environment: Compares against the highest environment (usually production) as defined in
.projenrc.ts:199. - Diff capture: Redirects both stdout and stderr to
cdk-diff-output.txtwith|| trueto prevent failures from blocking the workflow. - Comment action: Uses
towardsthecloud/aws-cdk-diff-pr-commenter@v1to post formatted diff output as a PR comment. - Permissions: Requires
pull-requests: writeto post comments on PRs.
Generation: Created by createCdkDiffPrWorkflow in src/bin/cicd-helper.ts:29-94.
Build workflow
File: .github/workflows/build.yml
Purpose: Validates pull requests by running the Projen build process and detecting file mutations. Ensures generated files stay in sync with .projenrc.ts.
Configuration:
name: build
on:
pull_request: {}
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
env:
CI: "true"
steps:
- name: Checkout
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22.18.0
- name: Install dependencies
run: npm install
- name: build
run: npx projen build
- name: Find mutations
id: self_mutation
run: |-
git add .
git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT
- name: Upload patch
if: steps.self_mutation.outputs.self_mutation_happened
uses: actions/upload-artifact@v4.6.2
with:
name: repo.patch
path: repo.patch
overwrite: true
- name: Fail build on mutation
if: steps.self_mutation.outputs.self_mutation_happened
run: |-
echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch."
cat repo.patch
exit 1
self-mutation:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository)
steps:
- name: Checkout
uses: actions/checkout@v5
with:
token: ${{ secrets.PROJEN_GITHUB_TOKEN }}
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Download patch
uses: actions/download-artifact@v5
with:
name: repo.patch
path: ${{ runner.temp }}
- name: Apply patch
run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."'
- name: Set git identity
run: |-
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Push changes
env:
PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }}
run: |-
git add .
git commit -s -m "chore: self mutation"
git push origin "HEAD:$PULL_REQUEST_REF"
Key configuration details:
- Trigger: Runs on all pull requests and supports manual dispatch
- Build job: Runs
npx projen buildto compile TypeScript and regenerate files - Mutation detection: Checks if the build modified any files (indicates
.projenrc.tschanges weren't committed) - Self-mutation job: Automatically commits and pushes changes back to the PR branch if mutations are detected
- PROJEN_GITHUB_TOKEN: Required for the self-mutation job to push commits. See Prerequisites for setup instructions.
- Fork handling: Self-mutation is skipped for PRs from forks (requires manual updates)
Generation: Created by Projen's built-in build workflow generator (enabled by default in AwsCdkTypeScriptApp).
Auto-approve workflow
File: .github/workflows/auto-approve.yml
Purpose: Automatically approves and enables auto-merge for Dependabot pull requests with the auto-approve label.
Configuration:
name: auto-approve
on:
pull_request_target:
types:
- labeled
- opened
- synchronize
- reopened
- ready_for_review
jobs:
approve:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'dependabot' || github.event.pull_request.user.login == 'dependabot[bot]')
steps:
- uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v5
- name: Enable Pull Request Automerge
run: gh pr merge --merge --auto "${{ github.event.pull_request.number }}"
env:
GH_TOKEN: ${{ secrets.PROJEN_GITHUB_TOKEN }}
Key configuration details:
- Trigger: Runs on
pull_request_targetfor Dependabot PRs with specific events - Conditions: Only runs for Dependabot PRs with the
auto-approvelabel - Auto-approve: Uses
hmarr/auto-approve-actionto approve the PR - Auto-merge: Uses GitHub CLI to enable auto-merge on the PR
- PROJEN_GITHUB_TOKEN: Required for enabling auto-merge. See Prerequisites for setup instructions.
- Permissions: Requires
pull-requests: writeandcontents: write
Generation: Created by Projen's auto-approve workflow with custom merge step added in .projenrc.ts:119-135.
Pull request lint workflow
File: .github/workflows/pull-request-lint.yml
Purpose: Validates that pull request titles follow Conventional Commits format.
Configuration:
name: pull-request-lint
on:
pull_request_target:
types:
- labeled
- opened
- synchronize
- reopened
- ready_for_review
- edited
merge_group: {}
jobs:
validate:
name: Validate PR title
runs-on: ubuntu-latest
permissions:
pull-requests: write
if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target')
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |-
feat
fix
chore
requireScope: false
Key configuration details:
- Trigger: Runs on PR events and when PRs are edited
- Validation: Enforces Conventional Commits format with types:
feat,fix,chore - Scope: Scope is optional (
requireScope: false) - Examples of valid titles:
feat: add user authenticationfix: resolve deployment timeoutchore(deps): update dependencies
Generation: Created by Projen's built-in pull request lint workflow (enabled by default).
Common workflow steps
All deployment workflows share these common steps, generated by getCommonWorkflowSteps in src/bin/cicd-helper.ts:362-378:
- Checkout repository: Uses
actions/checkout@v5to clone the repository code. - Setup Node.js: Uses
actions/setup-node@v6with the version from.projenrc.ts:9(currently22.18.0) and enables npm caching. - Configure AWS credentials: Uses
aws-actions/configure-aws-credentials@v4to assume the OIDC role for keyless authentication. - Install dependencies: Runs
npm cifor reproducible dependency installation frompackage-lock.json.
How workflows are generated
All GitHub Actions workflows are generated from the configuration in .projenrc.ts. Here's how the generation works:
1. Environment configuration
Define your environments in .projenrc.ts:149-152:
const environmentConfigs: (EnvironmentConfig & { name: Environment })[] = [
{ name: 'test', accountId: '987654321012', enableBranchDeploy: true },
{ name: 'production', accountId: '123456789012', enableBranchDeploy: false },
];
2. Task generation
For each environment, addCdkActionTask (.projenrc.ts:165) creates npm tasks with environment-specific variables:
addCdkActionTask(project, {
CDK_DEFAULT_ACCOUNT: config.accountId,
CDK_DEFAULT_REGION: awsRegion,
ENVIRONMENT: config.name,
GITHUB_DEPLOY_ROLE: githubRole,
GIT_BRANCH_REF: '$(echo ${GIT_BRANCH_REF:-$(git rev-parse --abbrev-ref HEAD)})', // For branch deploys
});
This generates tasks like:
npm run test:synth- Synthesize test environment stacksnpm run test:deploy:all- Deploy all test environment stacksnpm run test:branch:deploy:all- Deploy all test environment stacks with branch suffixnpm run test:branch:destroy:all- Destroy all test environment branch stacks
See addCdkActionTask in src/bin/env-helper.ts:69-116 for the complete task generation logic.
3. Workflow generation
The createCdkDeploymentWorkflows function (.projenrc.ts:184-193) generates GitHub Actions workflows for each environment:
createCdkDeploymentWorkflows(
project.github,
config.accountId,
awsRegion,
config.name,
githubRole,
nodeVersion,
config.enableBranchDeploy, // Controls branch deploy workflow generation
orderedEnvironments, // Determines workflow chaining order
);
This creates:
cdk-deploy-<env>.yml- Main deployment workflowcdk-deploy-<env>-branch.yml- Branch deployment workflow (ifenableBranchDeploy: true)cdk-destroy-<env>-branch.yml- Branch cleanup workflow (ifenableBranchDeploy: true)
The CDK diff PR workflow is created separately by createCdkDiffPrWorkflow (.projenrc.ts:197-204), which compares against the highest environment (usually production).
4. Regenerate workflows
After modifying .projenrc.ts, run:
npx projen
This regenerates all workflow files in .github/workflows/ based on your updated configuration.
Monitoring and troubleshooting
- GitHub Actions UI: track the workflow progress directly in
Actions. Logs show the exact Projen task executing so you can reproduce locally. - CloudFormation stack events: The stack names match
createEnvResourceName('StackName'), so failures are easy to locate in the AWS console. - Rerunning: Use
workflow_dispatchor the "Re-run jobs" button after fixing issues. Just ensure the branch you rerun from still matches the intended state.
Before you push
- Run through the Local Development guide to synth/diff locally and keep formatting tidy.
- If you changed
.projenrc.ts, runnpx projenso the generated workflows stay in sync with the new environment layout.
Advanced configuration
- Edit
environmentConfigsin.projenrc.tsto change deployment order, AWS accounts, or to opt environments in/out of branch deploys. - Need additional stages (for example,
staging)? Add them to the array, rerunnpx projen, and newcdk-deploy-<env>workflows will appear automatically. - To adjust step logic or concurrency, modify
createCdkDeploymentWorkflowsand rerun Projen.
Ready to experiment with feature branches? Continue with Branch-based Development.