Set up CI/CD for containerized React App using Docker, AWS CodeBuild, AWS ECS, AWS CodePipeline & Github
Tags: aws, react, devops, cicd
Published: 2020-05-07 02:49:44

---
series: DevOps for Frontend Engineers
---
This is the last part of the series "Dev-ops for Front-End developers". I assume you already have:

-[Containerized your React Application using Docker](https://dev.to/mubbashir10/containerize-react-app-with-docker-for-production-572b)
-[Deployed on AWS ECS using Fargate](https://dev.to/mubbashir10/deploy-your-react-app-to-ecs-fargate-38p9)
-[Attached ELB and domain with the Container](https://dev.to/mubbashir10/attach-domain-to-elastic-load-balancer-elb-3e45)
-[Attached SSL to ELB & Enabled HTTPS](https://dev.to/mubbashir10/attach-ssl-to-elastic-load-balancer-elb-and-enable-https-3noi)
-Setup Github repo for your project and pushed your code to it

#### 1. Setting up CodeBuild Project
From the AWS console, navigate to CodeBuild. From the CodeBuild's homepage, select "Create project".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yk3808vkmxsg2fil2ii4.png)


A CodeBuild project has 6-7 parts (at the time of writing):

##### Project Configuration
Enter in the name (required), description (optional) and tags (optional). Click on "Enable build badge" if you want to show build pass/fail badge on your Github repo page.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p5mljderwb0zbot3vvmk.png)


##### Source
Select Github, select "Repository in my Github Account", click on "Connect using OAuth (you can also use access token method if you prefer)", then click on "Connect to GitHub". It will ask you to sign in and authorize, if you intend to authorize repositories from your organization then you will also have to grant access to the organization. Afterward, it will ask you to enter your Github password.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zai9d8uoijzu5l49crj8.png)

After entering the Github password, it will take you to the CodeBuild page and from there select "Confirm".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g7hn3ybj3su35gss9e5u.png)

Once authorized, you will be taken back to the CodeBuild. Search and select your repo ("my-app" in my case), then enter the branch name in the Source version field (the branch from which you would like to build e.g. master in my case).
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rkkuir82k8abj40fzqzf.png)

##### Primary source webhook events
Leave it unchecked as we will be triggering build using code pipeline.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/it6ung1lr67vfng559uf.png)

##### Environment
Select:
-"Managed Image" as an Environment image
-"Ubuntu" as an operating system
-"Standard" as a Runtime
-The latest version in Image dropdown ("aws/codebuild/standard:4.0" is the latest at the time of writing)
-"Always use the latest image for this runtime version" as Image version
-"Linux" as Environment type
-Enable the "Privileged" flag
-"New Service Role" as Service role (it will fill in the next field automatically, you can edit if you prefer some custom name)
-Leave Additional configuration as it is (unless you need to increase compute capacity etc.)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ysrcgpv0gteijgen7jnc.png)


##### Buildspec
Enter "Buildspec.prod.yml" in the Buildspec name field (we will create this file later on).
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/778cb6wa3xybzy7gxthl.png)

##### Artifacts & Logs
Leave these sections and click on "Create build project"
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1jwghy0mwdcked16ieq5.png)

##### Create and push Buildspec file

Create a new file in your project (react's app) root dir and name it "Buildspec.prod.yml" and paste the following snippet into it.

```
version: 0.2
phases:
  install:
    runtime-versions:
      docker: 19
  pre_build:
    commands:
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
      - REPOSITORY_URI=681373743177.dkr.ecr.us-east-2.amazonaws.com/my-app
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - docker build -t $REPOSITORY_URI:latest -f Dockerfile.prod .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - printf '[{"name":"my-app-default-container","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
  files: imagedefinitions.json


```
Replace the container name ("my-app-default-container") with the one that you used while creating Task Definition in earlier articles. Replace `YOUR_ECR_IMAGE_URI_HERE` with URI of your image, which you can get from AWS ECR.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0y70azhm89i7ixhat757.png)
Save the file, commit, and push to your Github repo.

Note*: Make sure you are providing your Dockerfile name at "-f Dockerfile.prod" in the snippet above.

##### Give Access to CodeBuild

###### ECR Permissions
Now you need to give AWS CodeBuild access to your AWS ECR repo. To do that, go back to ECR and click on your repo. Inside your repo, click on "Permissions" from the left sidebar.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7z3ede6recmdf2ja0zkv.png)

Click "Edit Policy JSON" and add the following JSON to the popup and click Save.

```
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "new statement",
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
      ]
    }
  ]
}
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/noy0rcunbhp2egkswg4s.png)

###### CodeBuild Role Policies
From AWS console go to IAM and select "Policies" from the left sidebar. Inside the Policies' page click on "Create policy".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/b82slhvd62liuip5ezze.png)

Select JSON, enter the following snippet and click "Review Policy".
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ecr:CompleteLayerUpload",
                "ecr:GetAuthorizationToken",
                "ecr:UploadLayerPart",
                "ecr:InitiateLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage"
            ],
            "Resource": "*"
        }
    ]
}
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xqflg68f9k26fl7g9h4s.png)

Name your policy "CodebuildToECR", give a description if you want, and click "Create policy".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4x9u7yh8ua0b84c537wh.png)

Once the policy has been created, it's time to add the policy to the CodeBuild's service role (created earlier). For that select "Roles" from the left sidebar.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ltdya3bpzzvoedc46d1y.png)

Search and select the CodeBuild role that was created earlier.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hyu56fyt7mcfet3x5mcz.png)

Click "Attach Policy".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/atmdvpzvucnkg4a0y1xd.png)

Search for the policy we created earlier (i.e CodebuildToECR), select it and click "Attach Policy".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pltz4t2b032rw118fta8.png)

Now we are ready to build our project using CodeBuild. But we still need to automate the CodeBuild and Deploy to ECS steps, so follow along.

#### 2. Setting up CodePipeline
From the AWS console home, go to CodePipeline. Click "Create pipeline".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/91z6aw1m1bl2r6amvbuz.png)

Enter the pipeline name and click "Next".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/31ql6rj4xxbjxv7wsn2h.png)

Just like before select "Github" as the source provider, use OAuth to grant access, select and search the repo (my-repo in my case) and enter the branch name (master in my case). Click "Next".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/s86gjm9dia3j3o0dzix3.png)

Select "AWS CodeBuild" as the Build provider. Search and select the CodeBuild project we created earlier and click "Next".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/uaoo4a4ecft3t6xov75b.png)

Select "Amazon ECS" as the Deploy provider. Select cluster and service we created earlier (in previous articles) and click "Next". Review the configuration and click "Create pipeline".
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8ezafjoqef8xnuf8f4l5.png)

That's it. After creating the pipeline it will start to build & deploy automatically (the first time). Now, whenever you push to the master branch (or branch you provided earlier) pipeline will be triggered automatically. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vq8uudyzi05o7jrh9zvf.png)

You can set up notification rules using AWS Chatbot with Slack (that's what we use) or use SNS with any other service you prefer. That I will cover in some other series.

---

Technically the part we have done is CD only. CI comes into play when we want to merge branches/PRs etc, and that requires you to write test cases that are executed before merging. 

To implement CI (Continous Integration), we will use Github Workflows. Create a folder in your app's root dir. and name it ".github". Inside this folder create a sub-folder and name it "workflows". Inside that folder create a new file called "react.yml". You can also use the following commands to achieve that.

```
mkdir .github
cd .github
mkdir workflows
touch react.yml
```
Open up "react.yml" with a text editor and paste the following snippet:
```
name: React CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: yarn
    - run: yarn test
```
Save the file. Commit the changes and push to your Github repo. That's it. Now, whenever you make changes to your code and create new PRs it will run test cases automatically. You can check your workflow by going to the "Actions" tab on Github within your repo.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/prd51zeaipw6qy72k5ga.png)

Further reading: https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment