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

  1. --require-approval never skips the IAM diff prompt. Review IAM changes in cdk diff output or pull request checks before merging rather than at deploy time.
  2. The cdk.context.json file should be committed - pipelines need it for SSM lookups cached in Chapter 2 to avoid live SSM calls on every run.
  3. CloudFormation automatically rolls back a failed deployment to the previous stable state. No extra rollback configuration is needed in the pipeline.
  4. 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.