Building a CodePipeline with AWS CDK (TypeScript)
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) orpublic_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:
- Build a basic pipeline from CodeCommit → CodeBuild
- Pull from a CodeCommit repository in another AWS account
- 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.