This is Chapter 5 of the Infrastructure as Code with AWS CDK series. The same cdk deploy command that runs locally works in any pipeline - the differences are auth and orchestration.
Common flags for CI
These apply regardless of which CI system you use:
cdk synth # generate CloudFormation template
cdk deploy --require-approval never -c env=prod # deploy without interactive prompts
cdk deploy --outputs-file outputs.json # write stack outputs to file
--require-approval never skips the IAM change confirmation that CDK shows interactively. Required in CI.
-c env=prod passes the environment context from Chapter 2.
GitHub Actions
Auth
OIDC is the recommended auth method - no long-lived credentials stored in GitHub. The pipeline assumes an IAM role directly via GitHub’s OIDC provider.
Create the OIDC provider in AWS once per account:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
Create an IAM role with a trust policy scoped to your repository:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
}
}
}]
}
Attach whatever IAM policies the CDK deploy needs (CloudFormation, S3, Lambda, IAM) to this role.
Workflow
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy-dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install CDK CLI
run: npm install -g aws-cdk
- name: Install dependencies
run: pip install -r requirements.txt -r requirements-dev.txt
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- name: Synth
run: cdk synth -c env=dev
- name: Test
run: pytest
- name: Deploy dev
run: cdk deploy --require-approval never -c env=dev
deploy-prod:
runs-on: ubuntu-latest
needs: deploy-dev
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install CDK CLI
run: npm install -g aws-cdk
- name: Install dependencies
run: pip install -r requirements.txt
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- name: Deploy prod
run: cdk deploy --require-approval never -c env=prod
The environment: production job pauses for a manual approval before running. Configure required reviewers under Settings > Environments > production in the GitHub repository.
Jenkins
Auth
The simplest setup: run the Jenkins agent on an EC2 instance with an IAM instance role. The CDK CLI picks up credentials automatically via the instance metadata service - no credentials to manage.
If the agent is not on EC2, store AWS credentials as a Jenkins credential (Credentials > Add > AWS Credentials) and inject them into the pipeline with the withCredentials block.
Jenkinsfile
pipeline {
agent any
environment {
AWS_DEFAULT_REGION = 'us-east-1'
}
stages {
stage('Install') {
steps {
sh 'pip install -r requirements.txt -r requirements-dev.txt'
sh 'npm install -g aws-cdk'
}
}
stage('Synth') {
steps {
sh 'cdk synth -c env=dev'
}
}
stage('Test') {
steps {
sh 'pytest'
}
}
stage('Deploy dev') {
steps {
sh 'cdk deploy --require-approval never -c env=dev'
}
}
stage('Approve prod') {
steps {
input message: 'Deploy to production?', ok: 'Deploy'
}
}
stage('Deploy prod') {
steps {
sh 'cdk deploy --require-approval never -c env=prod'
}
}
}
post {
failure {
echo 'Pipeline failed - check CloudFormation events for rollback details'
}
}
}
The input step pauses the pipeline until someone clicks Deploy in the Jenkins UI.
If credentials are stored in Jenkins rather than on an instance role, wrap the deploy steps:
withCredentials([[
$class: 'AmazonWebServicesCredentialsBinding',
credentialsId: 'aws-deploy-credentials'
]]) {
sh 'cdk deploy --require-approval never -c env=prod'
}
Azure DevOps
Auth
Store AWS credentials as secret pipeline variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or use the AWS Toolkit for Azure DevOps extension, which adds an AWSShellScript task with a configured service connection.
The example below uses secret variables directly, which works without the extension.
Pipeline
# azure-pipelines.yml
trigger:
branches:
include:
- main
variables:
pythonVersion: "3.12"
awsRegion: "us-east-1"
stages:
- stage: DeployDev
displayName: Deploy to dev
jobs:
- job: Deploy
pool:
vmImage: ubuntu-latest
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: $(pythonVersion)
- script: |
pip install -r requirements.txt -r requirements-dev.txt
npm install -g aws-cdk
displayName: Install dependencies
- script: cdk synth -c env=dev
displayName: Synth
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
AWS_DEFAULT_REGION: $(awsRegion)
- script: pytest
displayName: Test
- script: cdk deploy --require-approval never -c env=dev
displayName: Deploy dev
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
AWS_DEFAULT_REGION: $(awsRegion)
- stage: DeployProd
displayName: Deploy to prod
dependsOn: DeployDev
jobs:
- deployment: Deploy
environment: production
pool:
vmImage: ubuntu-latest
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: UsePythonVersion@0
inputs:
versionSpec: $(pythonVersion)
- script: |
pip install -r requirements.txt
npm install -g aws-cdk
displayName: Install dependencies
- script: cdk deploy --require-approval never -c env=prod
displayName: Deploy prod
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
AWS_DEFAULT_REGION: $(awsRegion)
The deployment job type with environment: production enables approval gates. Configure approvers under Pipelines > Environments > production in Azure DevOps.
AWS CodePipeline
CDK ships a pipelines module that creates a CodePipeline pipeline directly from CDK code:
from aws_cdk import pipelines
pipeline = pipelines.CodePipeline(self, "Pipeline",
synth=pipelines.ShellStep("Synth",
input=pipelines.CodePipelineSource.git_hub("my-org/my-repo", "main"),
commands=[
"pip install -r requirements.txt",
"npm install -g aws-cdk",
"cdk synth"
]
)
)
The pipeline is self-mutating - when CDK code changes, the pipeline updates itself before deploying application changes. It is tightly coupled to CodePipeline and requires more upfront setup than the external CI approaches above, but is the right fit when the deployment needs to stay fully within AWS.
Notes
--require-approval neverskips the IAM diff prompt. Review IAM changes incdk diffoutput or pull request checks before merging rather than at deploy time.- The
cdk.context.jsonfile should be committed - pipelines need it for SSM lookups cached in Chapter 2 to avoid live SSM calls on every run. - CloudFormation automatically rolls back a failed deployment to the previous stable state. No extra rollback configuration is needed in the pipeline.
- For OIDC in GitHub Actions, scope the trust condition to a specific branch (
repo:my-org/my-repo:ref:refs/heads/main) rather than*to limit which workflows can assume the role.