Deploy React.js application using AWS S3 & GitLab pipelines for automatic deployment 2024
Tags: react, aws, javascript, devops
Published: 2024-05-31 14:00:55

In this article, we will look at how we can deploy our React.js application to the AWS S3 bucket using GitLab pipelines🚀

We'll look at how to automate our development process as much as possible🔧

---

## 1 - Project setup and creating GitLab repository 📁
I'll be using the React.js application I've initialized using the following command:

```js
npm create vite@latest
```

1. Login to your GitLab account ([Register](https://gitlab.com/users/sign_up) if you already don't have one)🔐

2. Create a repository for our project📦

3. Clone our repository using HTTP or SSH🔗

4. Commit our code to the repository using the following command💾

```js
git add .
git commit -m "Initial commit"
git push -u origin main
```

After, when we configure our S3 bucket, we will <u>come back</u> to GitLab to configure our pipeline which will do auto-deployment for us whenever we push new code to the `main` branch🔄

---

## 2 - Login to your AWS account and find the "S3" service🌐

Search for the "S3" service in the search input field which is located at the top of the page.

Choose an option with the green colored icon of a bucket with the description "Scalable Storage in the Cloud"🪣

You will see the following page. Click on the "Create bucket" button
![Creating a bucket on AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzb8vf9wfgl56v3ngyz3.png)

---

## 3 - Creating our S3 bucket🛠️

1. Bucket name: `our-react-app-s3-bucket`📝

2. **IMPORTANT**: Disable the "_Block all public access_" checkbox input field -> _this is important to enable our application to be publicly visible_🚫🔓

3. Click the "Create bucket" button✅
 
Now, we have successfully created our S3 bucket🎉

![AWS S3 bucket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7o3xhb1gpt3zofyey8t.png)

## Modify bucket policy📜

Now, you need to edit the **Bucket Policy**. Click the "Edit" button in the **Bucket Policy section**. In our case bucket name is `our-react-app-s3-bucket` so I will use it. Please change it according to your bucket name✏️

Paste the following code into your new policy:

```js
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::our-react-app-s3-bucket/*"
        }
    ]
}
```

---

## 4 - Create a new Identity Provider🔑

Search for the "Identity Provider" service in the search input field which is located at the top of the page.

![AWS Identity Provider](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m09cum9374gl3krjxdt.png)

On the left navigation pane, under Access management choose **Identity providers** and then choose **Add provider** button➕

For provider type, select **OpenID Connect**🌐

For **Provider URL**, enter the address of your GitLab instance, such as `https://gitlab.com` or `https://gitlab.example.com`🌍

For **Audience**, enter something that is generic and specific to your application. In my case, I'm going to enter `our-identity-provider`🎯

> To prevent confused deputy attacks, it's best to make this something that is not easy to guess🚫🕵️‍♂️

Take note of this value because you will use it to set the **ID_TOKEN** in your `.gitlab-ci.yml` file📝

Lastly, click on the "Add provider" button to finish up✅

## Create the permissions policy📜

After you create the identity provider, you need to create a **permissions policy**🛡️

From the IAM dashboard, under Access management select **Policies** and then **Create policy**➕

Select the JSON tab and paste the following policy replacing `our-react-app-s3-bucket` on the `Resource` line with <u>your</u> bucket name. Click the "Next" button to continue. For a "Policy name" input field type: `our-policy`. At the end, click the "Create policy" button to finish up📝

```js
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::our-react-app-s3-bucket"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": ["arn:aws:s3:::our-react-app-s3-bucket/*"]
    }
  ]
}
```

![IAM AWS policy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emqccdfq1uboyp4s1e7c.png)

**Note**: Don't change anything except the bucket name!🛑

---

## 5 - Creating a role🎭

Now it’s time to add the role. From the IAM dashboard, under **Access management** select **Roles** and then click **Create role** button. Select **Web identity**🆔

In the Web identity section, select the **identity provider** you created <u>earlier</u>. In our case, it would be `gitlab.com`🌐

For the **Audience**, select the audience you created earlier. Select the Next button to continue. In our case, it would be `our-identity-provider`🎯

Click the "Next" button➡️

If you wanted to limit authorization to a specific group, project, branch, or tag, you could create a Custom trust policy instead of a Web identity. Since I will be deleting these resources after the tutorial, I'm going to keep it simple😊

![AWS Roles web identity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xbt7yznegozgeq7kmwx.png)

Click the "Next button"➡️

During the **Add permissions**(step number 2), search for the policy you created and click the "Next" button to continue. In our case, it would be `our policy`🔍

![AWS Permissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfmrkgt07zbea5ct6iat.png)

In the next step(step number 3), give your role a name and click the "Create role" button. I will call it `our-role-name`📝

Search for the role you just created. Click on it🔍

In the summary section, find the **Amazon Resource Name (ARN)** and save it somewhere secure🔒

You will use this in your pipeline🔄

![AWS Arn](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wtu8b80t56c49hwonrr.png)

In our case, it is `arn:aws:iam::162340708442:role/our-role-name`✏️

---

## 6 - Deploy to your Amazon S3 bucket using a GitLab CI/CD pipeline🚀

Inside of your project repository on GitLab, create two CI/CD variables🔧 

> The first variable should be named `ROLE_ARN`. For the value, paste the **ARN** of the role you just created in the last step📝

> The second variable should be named `S3_BUCKET`. For the value, paste the name of the S3 bucket you created earlier. In our case it would be `our-react-app-s3-bucket`📝

## Retrieve your temporary credentials🗝️

Inside of your `.gitlab-ci.yml` file, paste the following code:

```js
.assume_role: &assume_role
    - >
      STS=($(aws sts assume-role-with-web-identity
      --role-arn ${ROLE_ARN}
      --role-session-name "GitLabRunner-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
      --web-identity-token $ID_TOKEN
      --duration-seconds 3600
      --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
      --output text))
    - export AWS_ACCESS_KEY_ID="${STS[0]}"
    - export AWS_SECRET_ACCESS_KEY="${STS[1]}"
    - export AWS_SESSION_TOKEN="${STS[2]}"
```

This is going to use the **AWS Security Token Service** to generate temporary (3,600 seconds) credentials utilizing the OIDC role you created earlier🛡️

## Create the deploy job🛠️

Now, let's add a build and deploy job to build your application and deploy it to your S3 bucket📦

First, update the stages in your `.gitlab-ci.yml` file to include a `build` and `deploy` stage as shown below:

```
stages:
  - build
  - test
  - deploy
```

Next, let's add a job to build your application. Paste the following code in your `.gitlab-ci.yml` file:

```js
build artifact:
  stage: build
  image: node:latest
  before_script:
    - npm install
  script:
    - npm run build
  artifacts:
    paths:
      - build/
    when: always
  rules:
    - if: '$CI_COMMIT_REF_NAME == "main"'
      when: always
```

This is going to run `npm run build` if the change occurs on the `main` branch and upload the build directory as an artifact to be used during the next step🔄

Next, let's add a job to actually deploy to your S3 bucket. Paste the following code in your `.gitlab-ci.yml` file:

```js
deploy s3:
  stage: deploy
  image:
    name: amazon/aws-cli:latest
    entrypoint: 
      - '/usr/bin/env'
  id_tokens:
      ID_TOKEN:
        aud: react_s3_gl
  script:
    - *assume_role
    - aws s3 sync build/ s3://$S3_BUCKET
  rules:
    - if: '$CI_COMMIT_REF_NAME == "main"'
      when: always
```

Your complete `.gitlab-ci.yml` file should look like this:

```js
stages:
  - build
  - test
  - deploy

.assume_role: &assume_role
    - >
      STS=($(aws sts assume-role-with-web-identity
      --role-arn ${ROLE_ARN}
      --role-session-name "GitLabRunner-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
      --web-identity-token $ID_TOKEN
      --duration-seconds 3600
      --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
      --output text))
    - export AWS_ACCESS_KEY_ID="${STS[0]}"
    - export AWS_SECRET_ACCESS_KEY="${STS[1]}"
    - export AWS_SESSION_TOKEN="${STS[2]}"
  
unit test:
  image: node:latest
  stage: test
  before_script:
    - npm install
  script:
    - npm run test:ci
  coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
  artifacts:
    paths:
      - coverage/
    when: always
    reports:
      junit:
        - junit.xml

build artifact:
  stage: build
  image: node:latest
  before_script:
    - npm install
  script:
    - npm run build
  artifacts:
    paths:
      - build/
    when: always
  rules:
    - if: '$CI_COMMIT_REF_NAME == "main"'
      when: always


deploy s3:
  stage: deploy
  image:
    name: amazon/aws-cli:latest
    entrypoint: 
      - '/usr/bin/env'
  id_tokens:
      ID_TOKEN:
        aud: react_s3_gl
  script:
    - *assume_role
    - aws s3 sync build/ s3://$S3_BUCKET
  rules:
    - if: '$CI_COMMIT_REF_NAME == "main"'
      when: always
```

---

## 7 - Make a change and test your pipeline🧪

1. Inside `App.js`, modify some code✏️ 
2. Commit your changes to the `main` branch. The pipeline should kick off and when it finishes successfully you should see your updated application at the URL of your static website🌐

Voila! 🎉

You now have a CI/CD pipeline built in GitLab that receives temporary credentials from AWS using OIDC and automatically deploys to your Amazon S3 bucket🚀

To take it a step further, you can secure your application with GitLab's built-in security tools🔒

## Where to find the URL to visit your website?🔗

Go to the "S3 buckets" page. Click on your bucket. Go to "Properties"🏠
Scroll to the bottom. You will find "Static website hosting". Click the "Edit" button✏️
Select the "Enable" radio checkbox✅

![AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ad8rtp4e8cxc35ckrbjw.png)

Click the "Save changes" button💾

In "Index document" fill: `index.html`📝

You will be redirected back. Again, scroll to the bottom of the page and you will find your website URL in the **Static website hosting** section🌐

![AWS S3 bucket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stak6i0wg7uvzkxvfxtl.png)

--- 

Thank you for reading. Hope I helped 😊

🚀 Follow me on [GitHub](https://github.com/perisicnikola37)