Automated React App Deployment from CodeCommit to S3 using CodePipeline

Automated React Deployment from Codecommit to S3
Sharing is Caring:

Gone are the days when companies used to deliver production deployments every few months.

Now, in the time of agile, customers are looking for MVP(Minimum Viable Product) as early as possible.

And after the MVP, they need frequent deployment of addition features. May be every 15 days or some companies even deploy every few hours.

You might think, one deployment takes so much time, how to do so many deployments.

Don’t worry !!!

CICD Pipelines are there for your rescue. Once automated, all you need to do is commit your code in your source code repository and rest all is taken care by the pipeline.

How to Deploy React App to S3?

Before we automate the react app deployment from CodeCommit to S3, let’s first understand what it takes to deploy a react app to AWS S3.

Once you create your react app and test it locally, you can simply create a production ready app by running below command.

npm run build

After this commands executes, it creates a build folder in root directory. It basically bundles your React app and minifies it into simple HTML, CSS, and JavaScript files.

It provides an entry point index.html, where your entire React app resides.

Hosting it on S3

Ideally, we have the optimized content of our react app in build folder. So all we need is to copy the content of build folder into an S3 bucket with website hosting enabled.

And you are done. 🙂

It’s that simple !!!

But wait a minute 🙂

Are you planning to copy the content of build folder manually into the bucket?

I am sure, you are not !!!

In this post, we will see how to create a CICD pipeline using CloudFormation so that your pipeline is automated and you can sleep peacefully.

All I mean is, you only worry about making your react app better and rest will be taken care by the pipeline.

Did I convince you enough to go through this post 😛

Well, let’s go then !!!

Steps to Automate React App Deployment from CodeCommit to S3 using CodePipeline

  1. Create a React App and Test Locally
  2. Create a BuildSpec file to be used by CodeBuild
  3. Zip your code to create a codecommit repo
  4. Prepare CloudFormation Template for Pipeline
  5. Create the Pipeline Stack
  6. Make some changes into the code and push to codecommit

Step 1: Create a React App and Test Locally

Let’s start with creating a new react app. If you already have one, please feel free to head straight to step 2.

I am using AWS Cloud9 IDE for this and it comes with latest npm version. Please feel free to use your favorite IDE.

Create new react app

npx create-react-app my-app  

Navigate to created app directory-

cd my-app

Start NPM server-

 npm start

Test Locally

After server is started, you can test your application on localhost. I am on cloud9 IDE and I will preview my running application from menu bar.

deploy react app to s3

Step 2: Create a BuildSpec file to be used by CodeBuild

We will be using AWS CodeBuild to build our react app and then copy the content of the build folder into S3 bucket.

CodeBuild needs a buildspec specification or commands that it needs to execute when it starts building our code.

You can even add test code and add a test command so that CodeBuild your test your code as well.

Create a file buildspec.yml into the root directory of your application like below.

react app deployment with CICD

Add the below content in your buildspec.yml file.

version: 0.2
phases:
  install:
    runtime-versions:
      nodejs: 12
    commands:
      # Install dependencies needed
      - npm install

      # Upgrade AWS CLI to the latest version
      - pip install --upgrade awscli
  pre_build:
    commands:
      # Build the react app to give an optimized production build
      - npm run build
  build:
    commands:
      # Copy the content of build folder into website s3 bucket
      - aws s3 sync build/ s3://$WEBSITE_S3_BUCKET/

Note : Please note that WEBSITE_S3_BUCKET in the command – aws s3 sync build/ s3://$WEBSITE_S3_BUCKET/ is an environment variable and is set during CodeBuild project creation.

Don’t worry about it now, you will see it in the CloudFormation template when we will prepare one for our pipeline.

Step 3: Zip your code to create a codecommit repo

One you are done testing your new app locally, we will create a CodePipeline using CloudFormation.

Our Pipeline will only have two stage.

  1. Source- CodeCommit
  2. Build -CodeBuild

While creating a codecommit repo, we can either create an empty repo and upload the code there or create it with your sample code.

As we already have sample code from above, so I would prefer to create the repo using it.

To do so, we need to create a zip containing all our code.

Note: Please make sure that extracting the zip file is directly giving you project structure and no nested folder. Also it should not contain any .git folder or .gitignore file

This is how it looks when I extract my-app.zip

react app folder structure

After you are ready with the zipped code, upload it to any S3 bucket you already have. We will need this bucket name and key while creating CodeCommit repo.

Step 4: Prepare CloudFormation Template for Pipeline

As we want a fully automated CICD pipeline so we will use CloudFormation to create our pipeline and other important resources.

Our template contains below resources.

  • An S3 Bucket to be used for website hosting
  • A read policy on website bucket to allow public read
  • An artifact S3 Bucket to be used by CodePipeline
  • A policy for artifact bucket to allow read , write by CodeBuild etc.
  • A CloudWatch event rule to trigger our pipeline on code changes
  • A CodeCommit repo to store our code
  • A CodeBuild project
  • A Toolchain Role
  • A Pipeline

Here is the final template.

AWSTemplateFormatVersion: 2010-09-09
Description: Pipeline for the react web app

Parameters:
  ProjectId:
    Type: String
    Description: Project ID.
    Default: my-app
  WebsiteS3Bucket:
    Type: String
    Description: Bucket Name
    Default: preetitest-website
  CodeBucket:
    Type: String
    Description: Bucket in which you have code
    Default: code-bucket
  CodeKey:
    Type: String
    Description: key of zipped code
    Default: test-react/my-app.zip
    

Resources:

  TargetS3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref WebsiteS3Bucket
      WebsiteConfiguration:
        ErrorDocument: 'index.html'
        IndexDocument: 'index.html'
      AccessControl: PublicRead
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false	  
  TargetS3BucketReadPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref TargetS3Bucket
      
      PolicyDocument:
        Statement:
          - Action: 
              - 's3:GetObject'
            Effect: Allow
            Resource: 
              - !Sub 'arn:${AWS::Partition}:s3:::${WebsiteS3Bucket}/*'
            Principal: '*'
            
  ArtifactBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: cloudkatha-artifact-bucket
   
  ArtifactBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Description: Setting Amazon S3 bucket policy for AWS CodePipeline access
    Properties:
      Bucket: !Ref ArtifactBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:GetObject'
              - 's3:GetObjectVersion'
              - 's3:GetBucketVersioning'
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::${ArtifactBucket}'
              - !Sub 'arn:${AWS::Partition}:s3:::${ArtifactBucket}/*'
            Effect: Allow
            Principal:
              AWS:
              AWS:
                - !GetAtt 
                  - ToolChainRole
                  - Arn
          - Action:
              - 's3:PutObject'
            Resource:
              - !Sub 'arn:${AWS::Partition}:s3:::${ArtifactBucket}'
              - !Sub 'arn:${AWS::Partition}:s3:::${ArtifactBucket}/*'
            Effect: Allow
            Principal:
              AWS:
                - !GetAtt 
                  - ToolChainRole
                  - Arn
        
  CodeCommitRepo:
    Type: 'AWS::CodeCommit::Repository'
    Description: Repo application source code
    Properties:
      RepositoryName: !Ref ProjectId
      Code:
        BranchName: main
        S3: 
          Bucket: !Ref CodeBucket
          Key: !Ref CodeKey
          
  CodeBuildProject:
    DependsOn:
      - ToolChainRole
    Type: 'AWS::CodeBuild::Project'
    Properties:
      Artifacts:
        Type: codepipeline
        Packaging: zip
      ServiceRole: !Ref ToolChainRole
      Environment:
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Value: !Ref TargetS3Bucket
            Name: WEBSITE_S3_BUCKET
        Image: 'aws/codebuild/standard:3.0'
        ComputeType: medium
      Source:
        Type: codepipeline
      Name: !Ref ProjectId
      

  ToolChainRole:
    Type: 'AWS::IAM::Role'
    Description: Creating role for codebuild
    Properties:
      Path: /
      RoleName: !Sub 'ToolChainRole-${ProjectId}'
      Policies:
        - PolicyName: ToolChainRolePolicy
          PolicyDocument:
            Statement:
              - Action:
                  - 'codecommit:*'
                  - 'codepipeline:*'
                  - 'codebuild:*'
                  - 's3:*'
                  - 'events:*'
                  - 'iam:PassRole'
                  - 'cloudwatch:*'
                  - 'kms:*'
                  - 'cloudformation:*'
                  - 'logs:*'
                Resource: '*'
                Effect: Allow

      AssumeRolePolicyDocument:
        Statement:
          - Action: 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
                - codedeploy.amazonaws.com
                - codepipeline.amazonaws.com
                - events.amazonaws.com
                
  SourceEvent:
    Type: 'AWS::Events::Rule'
    Properties:
      EventPattern:
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - !Join [ '', [ 'arn:aws:codecommit:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref ProjectId ] ]
        detail:
          referenceType:
            - branch
          event:
            - referenceCreated
            - referenceUpdated
          referenceName:
            - main
        source:
          - aws.codecommit
      Description: >-
        Rule for Amazon CloudWatch Events to detect changes to the source
        repository and trigger pipeline execution
      State: ENABLED
      Targets:
        - Id: ProjectPipelineTarget
          Arn: !Sub >-
            arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${ProjectId}-Pipeline
          RoleArn: !GetAtt
            - ToolChainRole
            - Arn
      Name: Demo_Source_Event

  DemoPipeline:
    DependsOn:
      - ToolChainRole
      - ArtifactBucket
      - CodeBuildProject
    Type: 'AWS::CodePipeline::Pipeline'
    Description: Creating an AWS CodePipeline
    Properties:
      RoleArn: !GetAtt
        - ToolChainRole
        - Arn
      Name: !Sub '${ProjectId}-Pipeline'
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Actions:
            - ActionTypeId:
                Owner: AWS
                Category: Source
                Version: 1
                Provider: CodeCommit
              Configuration:
                PollForSourceChanges: false
                RepositoryName: !Ref ProjectId
                BranchName: main
              InputArtifacts: []
              OutputArtifacts:
                - Name: !Sub '${ProjectId}-SourceArtifact'
              RunOrder: 1
              Name: ApplicationSource
          Name: Source
        - Actions:
            - ActionTypeId:
                Owner: AWS
                Category: Build
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref ProjectId
              InputArtifacts:
                - Name: !Sub '${ProjectId}-SourceArtifact'
              OutputArtifacts: []
              RunOrder: 1
              Name: PackageExport
          Name: Build
          
Outputs:
  WebsiteUrl:
    Description: URL of S3 Website
    Value: !GetAtt TargetS3Bucket.WebsiteURL

Step 5: Create the Pipeline Stack

Grab the template from above and change the value of below parameters.

  • ProjectId: An Id for your project(in lower case only)
  • WebsiteS3Bucket: Bucket name for your react app
  • CodeBucket: Bucket in which you have stored zipped code
  • CodeKey: Key of the zipped code

Save the template as pipeline.yml and follow below steps.

  • Login to AWS Management Console, navigate to CloudFormation and click on Create stack
  • Click on “Upload a template file”, upload pipeline.yml  and click Next
  • Enter the stack name and click on Next. In configuration, keep everything as default and click on Next.
  • In the events tab of stack, you can view the status.
  • Once stack is successfully created, go to Output tab
  • You can get the website URL from here and hit the endpoint
deploy a react aap to s3

On clicking above URL we get below screen- pretty basic what react create app provides as default

first webapp creation

Step 6: Make some changes into the code and push to codecommit

Now when we were able to see the first deployment, lets make some changes to code and push it to codecommit.

I am using cloud9 and it comes with AWS managed temporary credentials with my IAM user. So If my IAM user has CodeCommit access, I can clone the repo by doing-

CodeCommit Repo -> Clone URL -> Clone HTTPS

Going to terminal and issuing clone command like below.

 git clone https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-app

This clones the project for me into the IDE.

If you are using local computer, you will need HTTPS credentials that you can generate from your IAM user(if only you have permission) . You can follow this tutorial to setup your git locally with credentials.

Once project is cloned, let’s edit the App.js

Then issue below commands to push your changes.

git status
git commit -a -m "First try"
git push

After the code push, you can go to your pipeline and see that pipeline is triggerd.

Final pipeline status

Once your pipeline is successful, you can go to your website URL again and see that changes has been propagated.

Let’s got to the URL http://preetitest-website.s3-website-eu-west-1.amazonaws.com

Final REact app deployed to S3

For this tutorial URL was : http://preetitest-website.s3-website-eu-west-1.amazonaws.com

By the way, don’t try above URL because after writing this post, I am gonna delete everything. So by the time you will read this. above link will no longer work.

Conclusion

This post was all about react app deployment from CodeCommit to S3.

Finally, let’s summarize what we did in this post.

  • We created a react app from scratch and tested it locally
  • We learnt how to deploy a react app to S3
  • Created a CodeCommit repo with our sample code using CloudFormation
  • Created a Codepipeline using CloudFormation
  • Made changes to the code and pushed to CodeCommit
  • Verified the pipeline triggering on code push
  • Gathered the S3 website URL and checked the latest deployment

Enjoyed the content?

Subscribe to our newsletter below to get awesome AWS learning materials delivered straight to your inbox.

Don’t forget to motivate me by-

Suggested Read:

Sharing is Caring:

Leave a Reply

Your email address will not be published. Required fields are marked *