log
Swift Code Chronicles

AWS CDK CodePipeline Example with GitHub Integration

Published on January 15, 2025
Updated on January 17, 2025
45 min read
AWS

Introduction

In this post, we will explore how to create an AWS CodePipeline using AWS CDK. The pipeline includes integration with GitHub as the source, CodeBuild for building the application, and CloudFormation for deploying the resources. Additionally, we’ll configure a custom S3 bucket for storing pipeline artifacts.


Prerequisites

Before proceeding, ensure you have the following:

  1. AWS CDK installed and configured.
  2. A GitHub repository with the application code and a buildspec.yml file.
  3. A GitHub OAuth token stored in AWS Secrets Manager.
  4. Proper IAM roles configured for CodePipeline and CodeBuild.

Key Components

The pipeline has three stages:

  1. Source Stage: Pulls code from a GitHub repository.
  2. Build Stage: Uses CodeBuild to build the application.
  3. Deploy Stage: Deploys resources using CloudFormation.

Code Breakdown

Below is a detailed explanation of the CDK code used to create the pipeline.

1. Asset Bucket

An S3 bucket is created to store pipeline artifacts. The bucket has a lifecycle rule to delete files after 7 days.

const assetBucket = new s3.Bucket(this, '<construct id>', {
  bucketName: '<bucketName>',
  lifecycleRules: [
    {
      id: 'DeleteAfter7Days',
      enabled: true,
      expiration: cdk.Duration.days(7),
    },
  ],
});

2. IAM Role

The CodePipeline uses an existing IAM role specified by its ARN.

const codepipelineRole = iam.Role.fromRoleArn(this, '<construct id>', '<CODE_PIPELINE_ROLE_ARN>', {
  mutable: false,
});

3. Build Project

A CodeBuild project is configured to build the application. The project uses Amazon Linux 2 as the build environment.

const buildProject = new codebuild.PipelineProject(this, '<construct id>', {
  projectName: '<projectName>',
  role: codepipelineRole,
  queuedTimeout: cdk.Duration.hours(0.5),
  timeout: cdk.Duration.hours(0.5),
  buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec.yml'), 
  environment: {
    buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
    computeType: codebuild.ComputeType.SMALL,
    environmentVariables: {
      XX: {
        type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
        value: '<value>',
      },
    },
  },
});

Specifies the build specification file, buildspec.yml, which contains the build commands and configurations for the CodeBuild project. This ensures that the build process is defined and version-controlled alongside your application code.

4. Pipeline Definition

The pipeline is configured with three stages: Source, Build, and Deploy. It uses the previously defined S3 bucket and IAM role.

const pipeline = new codepipeline.Pipeline(this, '<construct id>', {
  pipelineName: '<pipelineName>',
  artifactBucket: assetBucket,
  role: codepipelineRole,
  pipelineType: codepipeline.PipelineType.V1,
});

5. Source Stage

The source stage pulls code from a GitHub repository using a webhook trigger. The GitHub OAuth token is securely retrieved from AWS Secrets Manager.

const sourceAction = new codepipeline_actions.GitHubSourceAction({
  actionName: 'GitHubSource',
  owner: '<GitHub account user>',
  repo: '<GitHub repository>',
  branch: '<branch>',
  oauthToken: cdk.SecretValue.secretsManager('github-token', {
    jsonField: 'oauthToken',
  }),
  output: sourceOutput,
  trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
  runOrder: 1,
});

Explanation:

  • cdk.SecretValue.secretsManager: This method securely fetches a secret stored in AWS Secrets Manager.
  • 'github-token': This is the name of the secret in Secrets Manager. The secret must already exist and contain the necessary OAuth token.
  • jsonField: 'oauthToken': If the secret is a JSON object, this specifies the field from which to retrieve the value. In this case, it fetches the oauthToken field.

This approach ensures that sensitive credentials like OAuth tokens are not hardcoded in the application, enhancing security.

6. Build Stage

The build stage executes the CodeBuild project defined earlier.

const buildAction = new codepipeline_actions.CodeBuildAction({
  actionName: 'Build',
  project: buildProject,
  input: sourceOutput,
  outputs: [buildOutput],
  role: codepipelineRole,
  runOrder: 2,
});

7. Deploy Stage

The deploy stage uses a CloudFormation action to create or update the stack.

const deployAction = new codepipeline_actions.CloudFormationCreateUpdateStackAction({
  actionName: 'CloudFormation-CreateUpdateStack',
  stackName: '<stackName>',
  adminPermissions: true,
  templatePath: buildOutput.atPath('cloudformation-template.yaml'),
  deploymentRole: codepipelineRole,
  replaceOnFailure: true,
  role: codepipelineRole,
  runOrder: 3,
});

Specifies the location of the cloudformation-template.yaml in the build artifact output. This template defines the resources to be deployed or updated during the deployment stage.

8. Adding Stages to the Pipeline

Finally, the stages are added to the pipeline.

pipeline.addStage({
  stageName: 'Source',
  actions: [sourceAction],
});

pipeline.addStage({
  stageName: 'Build',
  actions: [buildAction],
});

pipeline.addStage({
  stageName: 'Deploy',
  actions: [deployAction],
});

Complete code

import * as cdk from 'aws-cdk-lib';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import type { Construct } from 'constructs';

export class CodePipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /** asset bucket */
    const assetBucket = new s3.Bucket(this, `<construct id>`, {
      bucketName: `<bucketName>`,
      lifecycleRules: [
        {
          id: 'DeleteAfter7Days',
          enabled: true,
          expiration: cdk.Duration.days(7),
        },
      ],
    });

    const codepipelineRole = iam.Role.fromRoleArn(this, `<construct id>`, '<CODE_PIPELINE_ROLE_ARN>', {
      mutable: false,
    });

    const buildProject = new codebuild.PipelineProject(this, `<construct id>`, {
      projectName: `<projectName>`,
      role: codepipelineRole,
      queuedTimeout: cdk.Duration.hours(0.5),
      timeout: cdk.Duration.hours(0.5),
      buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec.yml'),
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        computeType: codebuild.ComputeType.SMALL,
        environmentVariables: {
          XX: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: '<value>',
          },
        },
      },
    });

    const pipeline = new codepipeline.Pipeline(this, `<construct id>`, {
      pipelineName: `<pipelineName>`,
      artifactBucket: assetBucket,
      role: codepipelineRole,
      pipelineType: codepipeline.PipelineType.V1,
    });

    const sourceOutput = new codepipeline.Artifact();
    const buildOutput = new codepipeline.Artifact();

    const sourceAction = new codepipeline_actions.GitHubSourceAction({
      actionName: 'GitHubSource',
      owner: '<GitHub account user>',
      repo: '<GitHub repository>',
      branch: '<branch>',
      oauthToken: cdk.SecretValue.secretsManager('github-token', {
        jsonField: 'oauthToken',
      }),
      output: sourceOutput,
      trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
      runOrder: 1,
    });

    const buildAction = new codepipeline_actions.CodeBuildAction({
      actionName: 'Build',
      project: buildProject,
      input: sourceOutput,
      outputs: [buildOutput],
      role: codepipelineRole,
      runOrder: 2,
    });

    const deployAction = new codepipeline_actions.CloudFormationCreateUpdateStackAction({
      actionName: 'CloudFormation-CreateUpdateStack',
      stackName: `<stackName>`,
      adminPermissions: true,
      templatePath: buildOutput.atPath(`cloudformation-template.yaml`),
      deploymentRole: codepipelineRole,
      replaceOnFailure: true,
      role: codepipelineRole,
      runOrder: 3,
    });

    pipeline.addStage({
      stageName: 'Source',
      actions: [sourceAction],
    });

    pipeline.addStage({
      stageName: 'Build',
      actions: [buildAction],
    });

    pipeline.addStage({
      stageName: 'Deploy',
      actions: [deployAction],
    });
  }
}

Conclusion

This example demonstrates how to build a robust CI/CD pipeline using AWS CDK. The integration with GitHub allows automatic triggers for every commit, while the deployment via CloudFormation ensures your infrastructure is always up-to-date.

With this pipeline, you can streamline the deployment process and focus more on building features for your application.

About

A personal blog sharing technical insights, experiences and thoughts

Quick Links

Contact

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 Swift Code Chronicles. All rights reserved