Lessons Learnt: CI/CD for React App on AWS S3

When I first started to host my React application (create-react-app) on AWS S3, I would test locally, build, copy the build artifacts into the S3 bucket and commit my code.

I wanted to try to set up a CI/CD pipeline for the first time to automatically deploy the artifacts to S3 every time I commit my code.


My wrong understanding of CI/CD

Before starting, I knew AWS CodePipeline had the capabilities to deploy committed files into S3 buckets. So, in my mind, I had this visualized:

CodeCommit >> CodePipeline >> S3

I would commit code into my repository (CodeCommit) and CodePipeline would pull my build artifacts into S3. So the first thing I did was to remove /build from .gitignore, which was the location of the build artifacts when you run the npm run build command.

AWS CodePipeline did its job well. Everything I committed into CodePipeline can be deployed into the S3 bucket. But the problem lies here: I did not want to deploy the entire repository into S3.

After spending an embarrassingly long time to figure out how to get CodePipeline to extract the build artifacts into S3, I realized that committing build artifacts isn’t good practice in the first place. I got the Continuous Deployment (CD) part of the process right with CodePipeline, but my Continuous Integration (CI) was incomplete. Testing and building the code should also be automated and can be achieved with AWS CodeBuild, similar to something like this:

Sample CI/CD process from AWS Quick Starts

I realized that CodeBuild can be a really powerful tool if you know how to use it.

AWS CodeBuild

AWS CodeBuild

I will not be going into details of setting up the CodePipeline, the repository and the AWS S3 bucket, as they are fairly straightforward configurations from the AWS Management Console. Instead, I will focus a little more on AWS CodeBuild.

It’s all about buildspec.yml

AWS CodeBuild reads the buildspec.yml file in the root directory of your code repository and executes all the steps you define within it. The reference information for buildspec.yml can be found here. I highly recommend you refer to it when writing your build specifications.

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 12
  pre_build:
    commands:
      # install npm dependencies/packages
      - echo Installing npm packages
      - npm install
  build:
    commands:
      # build
      - echo Building
      - npm run build

artifacts:
  files:
    # represents all files recursively
    - "**/*"
  # directory of files and subdirectories to include in build output artifacts
  base-directory: build

CodeBuild will go through your build specifications phase by phase. Since I developed the React app on node.js 12, using the same runtime to build made the most sense.

From my basic understanding of CodeBuild, it spins up an Ubuntu/Amazon Linux container with a runtime of your choice and loads your code in. Normally, I would execute a npm install first to download and install all necessary npm packages stated in package.json. Following that, executing npm run build would build the code and dump the build artifacts into the /build folder.

Within buildspec.yml, you can also define exactly what files will form the build output artifacts. In this example, the output artifacts will then be passed unto CodePipeline which will then deploy it into S3.

CodeCommit >> CodeBuild >> CodePipeline >> S3

Lessons Learned

Continuous Integration is just as important as Continuous Deployment when automating the entire process. The flexibility of AWS CodeBuild meant that it could potentially perform way more complex actions other than simply testing and building. I will explore more in future blog posts.

Thank you for reading!

Leave a comment

Your email address will not be published.