Open In App

Deploy MERN Stack on AWS EC2 with Docker via GitHub Actions

EC2 stands for Elastic Cloud Computing and is a service that AWS offers for running applications, hosting websites, processing data, and performing other computing operations.

How will we deploy our MERN application?

We will push our MERN project frontend and backend into two different repositories on GitHub from our local machine. After that, using GitHub actions, we will build a Docker image for both repositories separately. These images will be pushed to DockerHub and saved in two different repositories on DockerHub. On AWS, we will configure a self-hosted runner for both the front end and back end. When our image is pushed to DockerHub, then our AWS self-hosted runner will pull the image from DockerHub. After this, our application will run on EC2. First, we will deploy the backend, and then, in a similar way, we will deploy the frontend. Hurray! Now you have hosted the MERN stack application on EC2.



Deploy The Book Store Application On AWS EC2 Using Docker Through GitHub Actions

Here I have my “Book Store,” an application developed using the MERN stack. To deploy any MERN stack application, the process is similar.



Step 1: Create A Public Repository On DockerHub

Login to DockerHub and create two public repositories, one for the frontend and the other for the backend. Also, note the username and repository name that we will need when we write the CICD workflow for our frontend and backend applications.

Remember, we have to store our DockerHub username and password in GitHub secrets when we create a repository for our project on GitHub.

Step 2: Allowing Access To The MongoDB Database

So our first step is to allow access to MongoDB from anywhere on the internet. Sometimes MongoDB allocates our IP to access the database; because of that, we can only access it from our network. This happens because we tested our application locally many times. To access it from anywhere, log in to your MongoDB account and go to the network access of your database. This option is present in the left-site navigation bar when you open your database under the Security section.

Now you will see an IP access list for your database. If you see IP “0.0.0.0/0,” then it is accessible from anywhere. If you see any other IP address, delete that and enter the given IP address.

Step 3: Push An Application On GitHub And Add GitHub Secrets

There will be two folders for your MERN application. First for the backend, and second for the frontend.

Now create two different repositories for frontend and backend, and push frontend folder files into the frontend GitHub repository and backend folder files into the backend GitHub repository of your project.

Adding GitHub Secrets: Hardcoding our username, password, and API inside code is not good practice. To secure our credentials in the production environment, GitHub provides a feature to add secrets. These secrets can be our “env” variables that we add at the time of development, connection keys or our credentials.

Step 4: Creating An EC2 Instance

Log in to your AWS account as an IAM user. If you don’t have an IAM user, then create one and login with that user. Search for EC2 in the AWS console and click on the EC2 option appearing in the search result.

Click on EC2.

Step 5: Installing Docker On Our EC2 Instance

In previous steps, we successfully created our EC2 instance. Let’s connect them. Go to instances, click on our created instance, and click on “Connect.”

Use the below commands to install Docker on AWS EC2:

 sudo apt-get update
sudo apt-get install docker.io -y
sudo systemctl start docker
sudo docker run hello-world
docker ps

After running this command, we will get permission denied as output. To give permission, we have to run the following command:

sudo chmod 666 /var/run/docker.sock
sudo systemctl enable docker
docker --version

Successfully installed Docker

Docker Installation EC2 Terminal Logs

Step 6: Creating Self-Hosted Runners On Our EC2 Instance

We have to create a runner on EC2, and that should be run simultaneously. To do this, we have to Configuring the self-hosted runner application as a service. To do this, we have to go to our created repositories in Step 3. So first, we will create a runner for our repository, “BookStore-Backend”. The process to create runners for both runners is the same.

Creating a “BookStore-Backend” repository runner: Open our “BookStore-Backend” repository and click on settings. On the left navigation bar, you will see the option “Actions”. Click on that dropdown, and you will see the “Runners” option. Click on that.

What Happens After Running These Commands?

Commands under the download section:

Commands under the configure section:

When we run our configuration command, the output terminal looks like this, and it asks for some input. We select some information by default and just add the name of the runner, “aws-ec2”. To run this runner, we have to run the “./run.sh” command every time.

Configure A Self-Hosted Runner As A Service: To run this runner without running a command every time, we have to configure Runner as a service. To do this in the terminal, run the following commands:

sudo ./svc.sh install
sudo ./svc.sh start

After running these commands, our runner will go into an “idle” state. It will run continuously.

Creating a “BookStore-Frontend” repository runner:

Note: Our terminal is in our backend folder, so we have to come into the root directory. To do this, run the “cd . . ” command.

When we run our configuration command, the output terminal looks like this, and it asks for some input. We select some information by default and just add the name of the runner, “aws-ec2.”. To run this runner, we have to run the “./run.sh” command every time.

Configure a self-hosted runner as a service: To configure Runner as a service in the terminal, run the following commands:

sudo ./svc.sh install
sudo ./svc.sh start

After running these commands, our runner will go into an “idle” state. It will run continuously. We successfully created a self-hosted runner for both repositories in our EC2 instance.

Step 7: Creating A Dockerfile And Workflow For The Backend Repository

Creating A Dockerfile: To write a Dockerfile for our backend, go to your development IDLE and create a file with the name “Dockerfile” in the root directory of the backend folder.

Inside package.json, see how we are starting our backend server if you are configured like this (“start”: “node index.js”), then paste this Dockerfile and change the port number as mentioned in your backend code.

{
"name": "backend",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"mongoose": "^8.2.3",
"nodemon": "^3.1.0"
}
}
FROM node:alpine3.18
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
EXPOSE 5555
CMD [ "npm", "run", "start" ]

Creating A Workflow For Our Backend Application: Now create a “.github” folder under the “backend” folder. Inside the “.github” folder, create the “workflow” folder, and inside this folder, create the file “cicd.yml”.

Now we will write a workflow that will trigger GitHub actions when we push these updated files into our GitHub repository. I will keep my application details inside this. I will underline what you have to change inside this “cicd.yml”. At the time of changes, you have to remove details with an underline, update the file with your details, and also remove the underline from a particular section. In the curly bracket, I will mention what details you have to add.

name: Deploy BookStore-Backend (GitHub Repository Name)
on:
push:
branches:
- master (Branch where this repository is pushed previously)

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Login to docker hub
run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker Image
run: docker build -t sushantbagul/bookstore_backend . (DockerHub Username/Repository Name . )
- name: Publish Image to docker hub
run: docker push sushantbagul/bookstore_backend:latest (DockerHub Username/Repository Name:latest )


deploy:
needs: build
runs-on: self-hosted
steps:
- name: Pull image from docker hub
run: docker pull sushantbagul/bookstore_backend:latest (DockerHub Username/Repository Name:latest )
- name: Delete old container
run: docker rm -f bookstore-backend-container (Old Container Name if any/New container Name)
- name: Run Docker Container
run: docker run -d -p 5555:5555(Configured Port:Configured Port) --name bookstore-backend-container(New Container Name) -e MONGO_PASSWORD='${{ secrets.MONGO_PASSWORD }}' sushantbagul/bookstore_backend (DockerHub Username/Repository Name)

Push These Changes To The Backend Repository

Our GitHub action will look like this:

Step 8: Testing Our Backend

After successfully building and deploying our backend, we have to test it. To do this, we will require our EC2 instance public address. Copy your EC2 instance public address, paste it in the browser, and give your backend deployment port number.

The URL will be a combination of these: http://<Your-EC2-Public-IP>:<Allocated-Port-Number> My app URL is http://54.211.239.4:5555 Note: This URL is for demo purposes.

Adding Secrets To The Frontend Repository: Our frontend application needs a backend to run, and we have deployed our backend successfully, so we will add our backend server URL to GitHub secrets. Go to the GitHub secret page and give a name according to your frontend base URL configuration variable name. And in value, paste your backend deployment URL in the format “http://<Your-EC2-Public-IP>:<Allocated-Port-Number>” and save this secret.

Note: If you have hardcoded the base URL in the frontend, then you have to manually change the base URL to the backend URL. Example: If your hardcode URL looks like this: “post(‘http://localhost/books’, data),” then it will be changed to “post(‘http://54.211.239.4:5555/books’, data)” inside your pages.

Step 9: Creating A Dockerfile And Workflow For The Frontend Repository

Creating a Dockerfile: To write a Dockerfile for our frontend, go to your development IDLE and create a file with the name “Dockerfile” in the root directory of the frontend folder.

Dockerfile: We will select an alpine image for our frontend and build our app. And after building, serve that with Niginx. Copy the following Dockerfile and paste it in your Dockerfile.

Note: Before saving inside your frontend folder root directory, run the command “npm run build” and see which new file is being created in your folder structure. And give the same name as the new folder name in this copy command, “COPY –from=build /app/dist .” at the place of dist.

FROM node:alpine3.18 as build
# Build App
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
RUN npm run build
# Serve with Nginx
FROM nginx:1.23-alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf *
COPY --from=build /app/dist .
EXPOSE 80
ENTRYPOINT [ "nginx", "-g", "daemon off;" ]

Creating a Workflow for our frontend: Now create a “.github” folder under the “frontend” folder. Inside the “.github” folder, create the “workflow” folder, and inside this folder, create the file “cicd.yml”.

Now we will write a workflow that will trigger GitHub actions when we push these updated files into our GitHub repository. I will keep my application details inside this. I will underline what you have to change inside this “cicd.yml”. At the time of changes, you have to remove details with an underline, update the file with your details, and also remove the underline from a particular section. In the curly bracket, I will mention what details you have to add.

Note: If you have not allocated a port for the frontend, then it will by default run on port 5173, so we will write this in the last command as “5173:80.”

name: Deploy BookStore-Frontend (GitHub Repository Name)

on:
push:
branches:
- master (Branch where this repository is pushed previously)

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Login to docker hub
run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker Image
run: docker build -t sushantbagul/bookstore_frontend . (Dockerhub Username/Repository Name . )


- name: Publish Image to docker hub
run: docker push sushantbagul/bookstore_frontend:latest (DockerHub Username/Repository Name:latest )

deploy:
needs: build
runs-on: self-hosted
steps:
- name: Pull image from docker hub
run: docker pull sushantbagul/bookstore_frontend:latest (DockerHub Username/Repository Name:latest )
- name: Delete old container
run: docker rm -f bookstore-frontend-container (Old Container Name if any/New container Name)
- name: Run Docker Container
run: docker run -d -p 5173:80(Configured/Default Port:Configured/Default Port) --name bookstore-frontend-container (New Container Name) sushantbagul/bookstore_frontend (DockerHub Username/Repository Name)

Push These Changes To The Frontend Repository

Our GitHub action will look like this:

Step 10: Testing Our Frontend

After successfully building and deploying our frontend, we have to test it. To do this, we will require our EC2 instance public address. Copy your EC2 instance public address, paste it in the browser, and give your frontend deployment port number.

The URL will be a combination of these: http://<Your-EC2-Public-IP>:<Allocated-Port-Number/Default-Port> My app URL is http://54.211.239.4:5173 Note: This URL is for demo purposes.

Note: Remember to add the inbound rule to the EC2 instance and the correct port number that you have configured.

Demo For Testing our Book Store Application

We successfully deployed our backend application and frontend application on an EC2 instance, and it is working the same as in the local system.

A Recap Of the Steps That We Have Taken To Deploy The MERN Application

Conclusion

Using GitHub actions, we first deployed our backend application on our EC2 instance. By using our backend application’s public IP address, we configured our frontend BaseURL inside our frontend repository secrets. Then we deployed our front-end application on our EC2 instance. By performing all these steps, you can successfully deploy your MERN Stack application in AWS EC2 using Docker through GitHub actions

How To Deploy MERN Applications On AWS EC2 – FAQ’s

How Do I Allow Access To MongoDB From Anywhere?

To allow access to MongoDB from anywhere, log in to your MongoDB account, go to the network access of your database, and ensure that the IP access list includes “0.0.0.0/0” to allow access from anywhere.

How Do I Add GitHub Secrets?

To add GitHub secrets, go to the settings of your GitHub repository, navigate to “Secrets and Variables,” and click on “Actions.” Then, click on “New repository secrets” and add the required variables, such as DockerHub username and password.

How Do I Create A Dockerfile And Workflow For My Repositories?

To create a Dockerfile, write the Dockerfile for your backend and frontend applications. For workflows, create a YAML file that defines the steps for building and deploying your Docker images using GitHub actions.

How Do You Test Your Deployed Applications?

After deploying your applications, copy the EC2 instance public address and paste it in the browser along with the specified port number for your backend and frontend. Test the applications to ensure they are functioning as expected.

Tell The Steps Involved In Deploying MERN Application On AWS EC2 Instance Using GitHub Actions?

The steps involved in deploying your MERN application on AWS EC2 are:

  1. Push frontend and backend to GitHub
  2. Build Docker images and push to DockerHub
  3. Configure AWS self-hosted runners
  4. Deploy backend and frontend
  5. Test deployed applications

Article Tags :