Deploy React SPA with CodePipeline and CodeBuild using AWS CDK

January 23rd, 2020 1083 Words

There are plenty of tools and services for continuous delivery available. Most of them are either directly built into the source code management tools you already use, or perfectly integrate with them. You might be familiar with CircleCI, Travis CI, GitLab CI, or GitHub Actions.

Of course, AWS has its services for covering the needs of continuous integration and continuous delivery. With AWS CodePipeline you can manage and orchestrate the stages and tasks of a pipeline and AWS CodeBuild is a slick pay-per-use approach for executing commands in containerized environments.

Final Goal

With the example project on GitHub, you can create a CI/CD pipeline using AWS CodePipeline and deploy a React SPA written in TypeScript to an Amazon S3 bucket to have a static website.

The provided GitHub repository contains a basic application created with create-react-app and the pipeline consists of thee steps: checkout, build, and deploy.

Every push to the master branch of the repository will trigger a pipeline run and deployment. Of course, everything is configured with the AWS CDK.

AWS CodePipeline

Introduction

For most services, you configure a pipeline with a YAML configuration in your source code’s repository. A pipeline typically consists of stages and jobs inside those stages. Most of the time, you will end up with stages for code checkout or dependency installation, and of course for testing, building, and finally deploying your application.

Using the AWS CDK to describe the AWS CodePipeline and AWS CodeBuild resources, you can get rid of the language switch and implement the pipeline’s components in the same language as you write your application: TypeScript, for example.

Preliminaries

You need to have the AWS CDK installed on your computer, configured AWS environment variables for CLI usage, cloned the repository, and - of course - install all needed dependencies.

# Configure AWS CLI
$ > export AWS_ACCESS_KEY_ID="…"
$ > export AWS_SECRET_ACCESS_KEY="…"
$ > export AWS_SESSION_TOKEN="…"

# Install AWS CDK
$ > yarn global add aws-cdk

# Clone GitHub repository
$ > git clone https://github.com/sbstjn/cra-pipeline.git
$ > cd cra-pipeline

# Install dependencies
$ > yarn install

GitHub Token

To access your source code located at GitHub, you need to configure a personal access token and store it somewhere in AWS so AWS CodePipeline can access it. The AWS Secrets Manager is a perfect fit for this job.

Besides the permission to access your (private) repositories, the token needs to have permission to configure Webhooks in your GitHub repository. After you created the access token, create a new secret in AWS Secrets Manager with the token as the value.

$ > aws secretsmanager create-secret \
    --name GitHubToken \
    --secret-string abcdefg1234abcdefg56789abcdefg \
    --region us-east-1

{
  "ARN": "arn:aws:secretsmanager:us-east-1:123456789001:secret:GitHubToken-uNBxTr",
  "Name": "GitHubToken",
  "VersionId": "4acda3d1-877f-4032-b38e-17bc50239883"
}

Configuration

The project includes a config.ts file to configure the location of your source code and the AWS region in which you want to deploy your AWS CodePipeline and AWS CodeBuild resources.

export const config = {
  github: {
    owner: "sbstjn",
    repository: "cra-pipeline",
  },
  env: { region: "us-east-1" },
};

If you never used the AWS CDK in your AWS account or desired region, you need to bootstrap the AWS CDK basics.

$ > yarn cdk bootstrap --region us-east-1

⏳  Bootstrapping environment aws://123456789001/us-east-1...

0/2 | 5:06:49 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket
0/2 | 5:06:50 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket Resource creation Initiated
1/2 | 5:07:11 PM | CREATE_COMPLETE      | AWS::S3::Bucket | StagingBucket

✅  Environment aws://123456789001/us-east-1 bootstrapped.

Deploy AWS CodePipeline and AWS CodeBuild

Thanks to the AWS CDK, the deployment of the infrastructure is nothing more than using an additional command:

$ > yarn cdk deploy Pipeline

Pipeline: deploying...
Pipeline: creating CloudFormation changeset...

✅  Pipeline

Outputs:
Pipeline.WebsiteURL = http://pipeline-files8e6940b8-3p9gac9qjzax.s3-website-us-east-1.amazonaws.com

The WebsiteURL will point to an empty Amazon S3 bucket. When you push a new commit to the master branch of your repository, AWS CodePipeline is triggered using GitHub Webhooks and AWS CodeBuild will build your React application. If the checkout of your sources and the React build process finished without errors, AWS CodePipeline will copy all static files to your S3 bucket. Done.

The Details

I try to store all AWS CDK files in a infra folder; You’ll find a folder for the CloudFormation Stacks, a buildspec.yml file for AWS CodeBuild and a index.ts for AWS CDK. All the interesting parts are stored in infra/stacks/pipeline.ts and I’ll try to briefly walk you through the contents.

First things first: You need an S3 bucket to store the static files in the end.

// Amazon S3 bucket to store CRA website
const bucketWebsite = new S3.Bucket(this, "Files", {
  websiteIndexDocument: "index.html",
  websiteErrorDocument: "error.html",
  publicReadAccess: true,
});

To pass artifacts from one AWS CodeBuild task to another, you need artifacts:

// AWS CodeBuild artifacts
const outputSources = new CodePipeline.Artifact();
const outputWebsite = new CodePipeline.Artifact();

Now, of course, you need a pipeline in AWS CodePipeline!

// AWS CodePipeline pipeline
const pipeline = new CodePipeline.Pipeline(this, "Pipeline", {
  pipelineName: "Website",
  restartExecutionOnUpdate: true,
});

As mentioned in the introduction, you will end up with three stages and actions in your pipeline. Let’s start with the basic stage for downloading your sources from GitHub. This stage will read your GitHub access token from AWS Secrets Manager.

// AWS CodePipeline stage to clone sources from GitHub repository
pipeline.addStage({
  stageName: "Source",
  actions: [
    new CodePipelineAction.GitHubSourceAction({
      actionName: "Checkout",
      owner: props.github.owner,
      repo: props.github.repository,
      oauthToken: CDK.SecretValue.secretsManager("GitHubToken"),
      output: outputSources,
      trigger: CodePipelineAction.GitHubTrigger.WEBHOOK,
    }),
  ],
});

All artifacts of the initial action are stored in outputSources and will be used as the input for the next action to build your static files. The build process uses AWS CodeBuild, configured with a simple buildspec.yml file.

// AWS CodePipeline stage to build CRA website and CDK resources
pipeline.addStage({
  stageName: "Build",
  actions: [
    // AWS CodePipeline action to run CodeBuild project
    new CodePipelineAction.CodeBuildAction({
      actionName: "Website",
      project: new CodeBuild.PipelineProject(this, "BuildWebsite", {
        projectName: "Website",
        buildSpec: CodeBuild.BuildSpec.fromSourceFilename(
          "./infra/buildspec.yml"
        ),
      }),
      input: outputSources,
      outputs: [outputWebsite],
    }),
  ],
});

Again, the artifacts of this action need to be stored for further processing. This time, it’s the outputWebsite artifact and all you need to do now is to copy all files to your S3 bucket. AWS CodePipeline offers a great default action to handle this task!

// AWS CodePipeline stage to deployt CRA website and CDK resources
pipeline.addStage({
  stageName: "Deploy",
  actions: [
    // AWS CodePipeline action to deploy CRA website to S3
    new CodePipelineAction.S3DeployAction({
      actionName: "Website",
      input: outputWebsite,
      bucket: bucketWebsite,
    }),
  ],
});

That’s all. Nothing fancy, just a straight forward solution using native AWS features and components. Have a good day, thank you for reading, and happy continuous integration!


View on GitHub Source code is published using the MIT License.