Deploying a React Application on AWS ECS step-by-step guide

Deploying a React Application on AWS ECS step-by-step guide

Hello Everyone in Today’s blog We will see How to Deploy a Containerized React frontend Application on AWS ECS with SSL from Let’s Encrypt and Load Balancing. The Application will also scale Horizontally with Auto Scaling. AWS ECS (Elastic Container Service) is a highly scalable, high-performance container orchestration service that supports Docker containers and allows you to easily run and manage containerized applications on AWS. we will set up the necessary infrastructure to ensure our application is secure, scalable, and highly available. We'll go through the entire process step by step, covering topics such as creating an ECS cluster, setting up a load balancer, configuring SSL with Let's Encrypt, and enabling auto-scaling to handle varying loads effectively. By the end of this tutorial, you'll have a robust and scalable containerized React application running on AWS ECS. Let's dive in!

The Cloud Infrastructure

Step 1: Containerize the React Application

The first step is to Dockerize the React app. Since React builds static files, I used Nginx to serve them.

FROM node AS build

WORKDIR /app

COPY package*.json .
COPY tsconfig*.json .

RUN npm install
COPY . .
RUN npm run build

FROM nginx:stable-alpine AS production

COPY --from=build /app/dist /usr/share/nginx/html
RUN  rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/nginx.conf
RUN rm /etc/nginx/conf.d/default.conf

COPY react.615915.xyz.conf /etc/nginx/conf.d

RUN mkdir -p /etc/ssl/certs/ && \ 
    mkdir -p /etc/ssl/ 


COPY ./certs/fullchain.pem /etc/ssl/certs/fullchain.pem 
COPY ./certs/privkey.pem /etc/ssl/privkey.pem

RUN chmod 644 /etc/ssl/certs/fullchain.pem && \ 
    chmod 600 /etc/ssl/privkey.pem && \
    chown root:root /etc/ssl/privkey.pem

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

Build Stage:

  • Uses the Node.js base image to install dependencies (npm install) and build the application (npm run build).

  • The build output is stored in the /app/dist directory.

  • Production Stage:

    • Uses the Nginx base image to serve the built application.

    • Configures Nginx with a custom nginx.conf and SSL certificates for HTTPS.

    • Deletes default configurations and sets up the required SSL files (fullchain.pem, privkey.pem) with appropriate permissions.

  • Ports and CMD:

    • Exposes ports 80 and 443 for HTTP and HTTPS traffic.

    • Runs Nginx in the foreground with the daemon off command.

If You don’t have SSL Certificate Then you can easily get one from Let’s Encrypt. I will not discuss it to keep this blog short.

Step 2: Push The Docker image to AWS ECR

In this step, we'll push the Docker image of our React application to the Amazon Elastic Container Registry (ECR). This ensures that our container image is securely stored and easily accessible for deployment on AWS ECS.

1. Create an ECR Repository

First, we need to create a repository in ECR where we can push our Docker image.

  • Navigate to ECR:

    • In the AWS Management Console, search for "ECR" and select Elastic Container Registry.
  • Create Repository:

    • Click on "Create repository."

    • Enter a name for your repository, e.g., my-react-app.

    • Choose other settings as needed and click "Create repository."

2. Push the Image

Once you have created the ECR Repository you can easily push the docker image by viewing the push commands in the ECR page. Just make sure that you have AWS CLI installed and you are logged in.

Step 3: Set up an ECS cluster

What is ECS cluster

An ECS Cluster (Elastic Container Service Cluster) is a logical grouping of resources where you can run your containerized applications on AWS. Think of it as the "home base" for your Docker containers.

Setting up an ECS cluster is a crucial step in deploying your containerized application on AWS. Here's how you can do it:

1. Navigate to the ECS Service

  • AWS Management Console: Log in to your AWS account and navigate to the ECS (Elastic Container Service) from the AWS Management Console.

2. Create a New Cluster

  • Cluster Creation: Click on "Clusters" in the left-hand navigation pane and then click on the "Create Cluster" button.

  • Select Launch Type:

    • Networking Only (Fargate): Choose this if you want to use AWS Fargate, which lets you run containers without managing the underlying infrastructure.

    • EC2 Linux + Networking: Choose this if you want to manage your own EC2 instances that will host the containers.

3. Configure Cluster Settings

  • Cluster Name: Enter a name for your cluster, e.g., my-react-app-cluster.

4. Additional Configuration (for EC2 Launch Type)

  • Instance Configuration:

    • Choose the EC2 instance types and the number of instances you want in your cluster.
  • Key Pair:

    • Select an existing key pair or create a new one to allow SSH access to the EC2 instances if needed.

5. Create the Cluster

  • Review and Create:

    • Review your settings and click "Create" to set up the cluster.
  • Cluster Initialization:

    • AWS will initialize the cluster and provision the necessary resources. This may take a few minutes.

Step 4: Creating A Task Definition

A Task Definition is a blueprint that defines how containers should run in AWS ECS (Elastic Container Service). It specifies details like container images, CPU/memory requirements, networking configurations, and storage volumes. This is a crucial step in deploying your application on ECS.

Steps to Create a Task Definition

  • Navigate to the ECS Console: Log in to your AWS Management Console and go to the ECS service.

  • Select Task Definitions: In the ECS dashboard, click on the "Task Definitions" tab and then choose "Create new Task Definition."

  • Choose the Launch Type: Select the launch type for your application:

    • EC2: If you are using an EC2 cluster to host your containers.

    • Fargate: For a serverless option where AWS manages the underlying infrastructure.

  • Configure Task Settings: Fill in the following details:

    • Task Definition Name: Give a meaningful name to your task definition.

We Will then define an Essential Container. In Amazon ECS (Elastic Container Service), an essential container is a container within a task definition that is crucial for the task's execution. An essential container is marked as critical to the task. If it stops or fails, the entire task is considered to have failed. If an essential container exits, ECS will stop all other containers in the task and terminate the task. This ensures that the failure of a critical component leads to a controlled task shutdown.

In Image URI We will fill the Image URI of our docker image. Just make sure that it is a publicly accessible.
We will allow traffic only from port 443 on the container.

We will set a health check path so that the It can be determined if our Container if functioning properly.

The purpose of a health check in an ECS task definition is to ensure the containers within the task are running correctly and are responsive. Health checks periodically test the status of the container by pinging a specified endpoint or running a command.

Now Just click on “Create Task” and your task Will be created.

Step 5: Creating A ECS Service

Creating an ECS Service is essential for managing and maintaining the desired number of tasks running in your ECS cluster. This step ensures that your application remains available, scalable, and can handle varying loads effectively. Here’s how to do it:

1. Navigate to the ECS Console

  • Open the ECS Service: Log in to your AWS Management Console and navigate to the ECS service.

2. Create a New Service

  • Clusters: Select your ECS cluster from the list.

  • Services: In the cluster view, click on the "Services" tab and then click "Create" to start the service creation process.

3. Configure Service Settings

  • Launch Type: Choose the launch type for your service, either Fargate or EC2, depending on your setup.

  • Task Definition: Select the task definition you created earlier.

  • Service Name: Provide a name for your service, e.g., my-react-app-service.

4. Deployment Configuration

  • Number of Tasks: Specify the desired number of tasks you want to run simultaneously.

  • Deployment Type: Choose the deployment strategy. For most cases, the Rolling update strategy works well. In rolling update strategy, when you make changes to your application and push the updates to your repository, new containers with the updated code are deployed incrementally. Existing containers are only terminated once the newly deployed containers pass all health checks. This ensures a seamless and reliable transition, maintaining application availability throughout the update process.

Make Sure to select At least two subnets in different Availability zones. Make Sure that the Security Group allows HTTPS traffic.

Set up the load balancer

CONFIGURING AUTO SCALING

Auto scaling is a crucial feature for ensuring your application can handle varying loads efficiently by automatically adjusting the number of running tasks based on demand. Here’s how it is set up according to the screenshot:.

  1. Minimum and Maximum Number of Tasks:

    • Minimum number of tasks: 1

    • Maximum number of tasks: 2

  2. Scaling Policy Type:

    • Target tracking is selected. This means the number of tasks will increase or decrease based on a target value for a specific metric.
  3. Policy Name:

    • The policy is named "react-app-policy".
  4. ECS Service Metric:

    • The metric used for scaling is "ECSServiceAverageCPUUtilization".
  5. Target Value:

    • The target value for the metric is set to 70. It means that as soon as CPU Utilization Hits 70% new Tasks (Containers) Will be deployed to serve the increasing traffic.
  6. Cooldown Periods:

    • Scale-out cooldown period: 300 seconds (This ensures that after increasing the number of tasks, the system waits 5 minutes before considering another scale-out action. This period allows the new tasks to start up and stabilize.)

    • Scale-in cooldown period: 300 seconds (This means that after reducing the number of tasks, the system waits 5 minutes before considering another scale-in action. This period ensures that the reduction in tasks doesn't happen too rapidly, allowing the system to stabilize.)

Simply click on "Create," and voilà! Your application will be deployed in just a few minutes. 🎉 Enjoy watching your hard work come to life!

You can access the application Via the Application Load Balancer DNS name. But If you still face some Problems you can check the logs section in ECS service.

PROBLEMS I FACED WHILE DEPLOYING THE APPLICATION
1. Serving an SPA with Nginx

When Trying to access a Page Directly by typing example.com/about or example.com/aboutus. I used to get 404 errors meaning page not found.

The Problem: React is an SPA, meaning all routes should resolve to index.html. However, Nginx attempted to locate a physical file for each route, causing 404 errors. Made the following changes in the server block of nginx.

SOLUTION

Used the try_files $uri /index.html; directive in nginx.conf instead of try_files $uri $uri/ =404; to ensure that all requests are redirected to index.html, allowing React Router to handle them.

2 .Static Assets Not Loading

The Problem: Some images and assets didn’t load after deploying the application. Upon investigation, I realized it was because I used relative paths in the React code, like

 <img src="assets/logo.png" alt="Logo" />

SOLUTION
Instead of importing them:

import logo from './assets/logo.png';
<img src={logo} alt="Logo" />

Why This Happened:

  • React’s npm run build optimizes static assets by hashing their filenames and placing them in the /static directory. Directly referencing paths like assets/logo.png bypasses React’s asset pipeline, causing broken links.

Solution: Use ES6 Imports for Static Assets

  • Updated the React code to use:

      import logo from './assets/logo.png';
      <img src={logo} alt="Logo" />
    
  • Rebuilt the application to ensure assets were properly included in the build process.