DEV Community

Mubbashir Mustafa
Mubbashir Mustafa

Posted on • Edited on

Set up CI/CD for containerized React App using Docker, AWS CodeBuild, AWS ECS, AWS CodePipeline & Github

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
-Deployed on AWS ECS using Fargate
-Attached ELB and domain with the Container
-Attached SSL to ELB & Enabled HTTPS
-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

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

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

After entering the Github password, it will take you to the CodeBuild page and from there select "Confirm".
Alt Text

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

Primary source webhook events

Leave it unchecked as we will be triggering build using code pipeline.
Alt Text

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

Buildspec

Enter "Buildspec.prod.yml" in the Buildspec name field (we will create this file later on).
Alt Text

Artifacts & Logs

Leave these sections and click on "Create build project"
Alt Text

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




Enter fullscreen mode Exit fullscreen mode

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
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

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"
      ]
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode

Alt Text

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

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": "*"
        }
    ]
}


Enter fullscreen mode Exit fullscreen mode

Alt Text

Name your policy "CodebuildToECR", give a description if you want, and click "Create policy".
Alt Text

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

Search and select the CodeBuild role that was created earlier.
Alt Text

Click "Attach Policy".
Alt Text

Search for the policy we created earlier (i.e CodebuildToECR), select it and click "Attach Policy".
Alt Text

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

Enter the pipeline name and click "Next".
Alt Text

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

Select "AWS CodeBuild" as the Build provider. Search and select the CodeBuild project we created earlier and click "Next".
Alt Text

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

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

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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

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

Top comments (12)

Collapse
 
chris7721 profile image
Chris7721

Great. But I get this error when the code tries to build

YAML_FILE_ERROR Message: Unknown runtime named 'docker'. This build image has the following runtimes: dotnet, golang, java, nodejs, php, python, ruby

Collapse
 
mubbashir10 profile image
Mubbashir Mustafa • Edited

So it seems like in version 5 they have removed "Docker: 19" and from now onwards default docker runtime will be available only. To make this code work you can:

  • Either change the runtime to v4 or less (the article was written at the time when v3 was the latest available). dev-to-uploads.s3.amazonaws.com/i/...
  • Or remove this 👇🏽 (if you are using v5)
install:
    runtime-versions:
      docker: latest
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mubbashir10 profile image
Mubbashir Mustafa
Collapse
 
chris7721 profile image
Chris7721

Oh thanks! Would try this and see how it works.

Thread Thread
 
mubbashir10 profile image
Mubbashir Mustafa

Sweet. Please do share how it goes!

Thread Thread
 
chris7721 profile image
Chris7721

It worked, but having another issue, the build break when it tries to run this

$(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)

The error message is:
An error occurred (AccessDeniedException) when calling the GetAuthorizationToken operation: User: arn:aws:sts:::assumed-role/codebuild-main--build-service-role/AWSCodeBuild--5c2c-4dff-a514- is not authorized to perform: ecr:GetAuthorizationToken on resource: *

[Container] 2021/02/07 14:25:18 Command did not exit successfully $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email) exit status 255

Thread Thread
 
dascott2007 profile image
Damaris Adora

You have probably figured it out already. But for newer folks. You have to replace:
$(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
with:
"- aws ecr get-login-password | docker login --username AWS --password-stdin $YOUR-AWS-Repostitory-URI"

Example buildspec.yml file:

Image description

Collapse
 
mubbashir10 profile image
Mubbashir Mustafa

What have you selected as "Runtime" and "Image"?

Collapse
 
chris7721 profile image
Chris7721

I have just setup another pipeline. I made sure I followed your steps exactly as it is. And I still have the same error.

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=780571656781.dkr.ecr.us-east-2.amazonaws.com/spurts-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 .
- 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":"spurts-container","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.json

This is my Buildspec file

Thread Thread
 
swiftlee profile image
Jonathan Conlin

I removed the entire install section. Worked like a charm.

Collapse
 
rynebenson profile image
Ryne Benson • Edited

I need to setup a staging environment for me and my team to test code before we ship to production. Is there a way to add this as a step in this current process or do I need to setup a separate pipeline with the staging branch as a source?

Collapse
 
mubbashir10 profile image
Mubbashir Mustafa

A separate environment with a separate pipeline.