log
Swift Code Chronicles

Building a CodePipeline with AWS CDK (TypeScript)

Published on April 27, 2025
Updated on April 27, 2025
42 min read
AWS

With the advent of microservices and containerization, it has become common to build an end-to-end continuous integration/continuous deployment (CI/CD) pipeline using AWS services. In this article, we’ll walk through how to use AWS CDK (TypeScript) to create a CodePipeline that goes from CodeCommit to CodeBuild and then publishes artifacts to S3.

1. Project Structure Overview

Before you begin, assume you’ve installed the AWS CDK and initialized a TypeScript project. Your directory structure should look something like this:

my-cdk-project/
├── bin/
│   └── my-cdk-project.ts       // CDK application entry point
├── lib/
│   └── code-pipeline-stack.ts  // The Stack we're about to write
├── build-spec.yaml             // CodeBuild build specification
├── package.json
└── tsconfig.json

2. Core CDK Code

import * as cdk from 'aws-cdk-lib';
import { Duration } from 'aws-cdk-lib';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as codecommit from 'aws-cdk-lib/aws-codecommit';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as 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);

    // 1. Reference an existing S3 bucket to store artifacts
    const artifactBucket = s3.Bucket.fromBucketArn(this, 'ArtifactBucket', 'arn:aws:s3:::your-artifact-bucket');

    // 2. Reference existing IAM roles for pipeline execution permissions
    const pipelineRole = iam.Role.fromRoleArn(this, 'PipelineRole', 'arn:aws:iam::123456789012:role/CodePipelineRole', {
      mutable: false,
    });
    const eventBridgeRole = iam.Role.fromRoleArn(
      this,
      'EventBridgeRole',
      'arn:aws:iam::123456789012:role/EventBridgeRole',
      { mutable: false },
    );

    // 3. Create a CodeCommit repository
    const repository = new codecommit.Repository(this, 'MyRepo', {
      repositoryName: 'my-app-repo',
    });

    // 4. Define a CodeBuild project
    const buildProject = new codebuild.PipelineProject(this, 'BuildProject', {
      projectName: 'my-app-build',
      role: pipelineRole,
      queuedTimeout: Duration.hours(1),
      timeout: Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromSourceFilename('build-spec.yaml'),
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        computeType: codebuild.ComputeType.SMALL,
        privileged: true,
        environmentVariables: {
          ENV: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: 'prod',
          },
        },
      },
    });

    // 5. Define pipeline artifacts
    const sourceArtifact = new codepipeline.Artifact('SourceArtifact');
    const buildArtifact = new codepipeline.Artifact('BuildArtifact');

    // 6. Configure the Source stage: watch the prod branch in CodeCommit
    const sourceAction = new actions.CodeCommitSourceAction({
      actionName: 'Fetch_Source',
      repository,
      branch: 'prod',
      output: sourceArtifact,
      role: pipelineRole,
      eventRole: eventBridgeRole,
      trigger: actions.CodeCommitTrigger.EVENTS,
      runOrder: 1,
    });

    // 7. Configure the Build stage: invoke CodeBuild
    const buildAction = new actions.CodeBuildAction({
      actionName: 'Run_Build',
      project: buildProject,
      input: sourceArtifact,
      outputs: [buildArtifact],
      role: pipelineRole,
      runOrder: 2,
    });

    // 8. Create the pipeline and add stages
    const pipeline = new codepipeline.Pipeline(this, 'CodePipeline', {
      pipelineName: 'MyAppPipeline',
      artifactBucket,
      role: pipelineRole,
    });
    pipeline.addStage({ stageName: 'Source', actions: [sourceAction] });
    pipeline.addStage({ stageName: 'Build', actions: [buildAction] });
  }
}

3. Sample build-spec.yaml

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 22
  pre_build:
    commands:
      - npm run lint
      - npm run test
  build:
    commands:
      - npm ci
      - npm run build
      - npx cdk deploy <StackName> --require-approval never

4. Cross-Account CodePipeline

If you need to pull from a CodeCommit repo in another AWS account (Account A) and run the pipeline in your own account (Account B), follow these steps.

4.1. In Account A, create a cross-account access role

  • In Account A’s IAM console, create a new role (e.g., cross-account-access-role).
  • In its trust policy, allow Account B’s root account or the pipeline service role to sts:AssumeRole:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::ACCOUNT_B_ID:root" },
      "Action": "sts:AssumeRole"
    }
  ]
}

4.2. Attach read-only permissions to that role

  • AWS managed policy AWSCodeCommitReadOnly
  • If using KMS-encrypted repos, grant kms:Decrypt, kms:GenerateDataKey*, etc., and allow the role in the CMK key policy

4.3. In Account B, import the external repo and role

// Import the CodeCommit repo from Account A
const externalRepo = codecommit.Repository.fromRepositoryArn(
  this,
  'ExternalRepo',
  'arn:aws:codecommit:ap-northeast-1:ACCOUNT_A_ID:MySharedDemoRepo',
);

// Import the cross-account access role from Account A
const crossAccountRole = iam.Role.fromRoleArn(
  this,
  'CrossAccountRole',
  'arn:aws:iam::ACCOUNT_A_ID:role/cross-account-access-role',
);

4.4. Configure the Source action to use the cross-account role

const sourceArtifact = new codepipeline.Artifact('SourceArtifact');

const crossAccountSource = new codepipeline_actions.CodeCommitSourceAction({
  actionName: 'CrossAccount_Source',
  repository: externalRepo,
  branch: 'main',
  output: sourceArtifact,
  role: crossAccountRole,
  trigger: codepipeline_actions.CodeCommitTrigger.EVENTS,
});

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

Now CodePipeline will assume the role in Account A to fetch code.


5. Using GitHub as the Repository

CodePipeline can also pull code from GitHub.

5.1. Create a Personal Access Token on GitHub

  • Log in to GitHub → avatar → Settings → Developer settings → Personal access tokens → Fine-grained personal access tokens → Generate new token
  • Grant at least repo (private repo read) or public_repo (public repo read) scopes, then copy the token.

5.2. Store the token in AWS Secrets Manager

aws secretsmanager create-secret \
  --name my-github-token \
  --description "GitHub OAuth token for CodePipeline" \
  --secret-string "<YOUR_TOKEN_HERE>"

5.3. Reference the secret and configure GitHubSourceAction in CDK

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

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

    // … previous setup …

    const oauthToken = cdk.SecretValue.secretsManager('my-github-token');

    const sourceOutput = new codepipeline.Artifact('SourceOutput');

    const githubSource = new codepipeline_actions.GitHubSourceAction({
      actionName: 'Checkout_From_GitHub',
      owner: 'your-github-username-or-org',
      repo: 'your-repo-name',
      branch: 'main',
      oauthToken: oauthToken,
      output: sourceOutput,
      trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
    });

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

6. Summary

In this article, we demonstrated how to use AWS CDK (TypeScript) to:

  1. Build a basic pipeline from CodeCommit → CodeBuild
  2. Pull from a CodeCommit repository in another AWS account
  3. Fetch code from GitHub and trigger builds automatically

From here, you can extend your pipeline with testing stages, deployments to Elastic Beanstalk, ECS, Lambda, and more, creating a robust and efficient CI/CD workflow.

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