Skip to main content

Command Palette

Search for a command to run...

Dockerizing a MERN app

Updated
5 min readView as Markdown
Dockerizing a MERN app

Introduction

This article is the first part of my series discussing DevOps In the MERN stack. We will discuss containerizing a MERN app to auto-deployment. In this article, we will cover what dockerizing means and how to dockerize a MERN app. But first, we will go over the basics.

What is the MERN stack?

The acronym MERN stands for MongoDB, Express, React and Nodejs and it is one of the stacks used in full stack javascript development.

  • MongoDB - a non-relational database that enables data to be stored in a JSON-like format.

  • Express - this is a backend Nodejs application framework that is used to build RESTful APIs.

  • React - this is a frontend javascript library used in building UI interfaces.

  • Nodejs - javascript runtime environment.

What is containerization and why should we do it?

Containerization is the packaging of software code with its files, libraries and frameworks to run on any infrastructure. The container is isolated from its host operating system and hence becomes portable. This means it will run on any machine regardless of the OS. Docker is an open-source platform as a service that is meant to assist developers to build, deploy, run and manage containers.

Containerization offers several benefits which include portability, speed, fault isolation, scalability and ease of management.

Example of a MERN project to dockerize.

For this tutorial, I will dockerize a simple document review application built using the MERN stack. The application allows users to upload and delete files that are reviewed by a review committee/person. The GitHub repository can be found here. You can check it out to understand the project structure. Below is a clip of it.

Dockerizing the frontend

To dockerize our MERN app we will need to add a dockerfile to both the React-app root directory and the server root directory. The final project structure should look like this;

project-directory/ 
  ├── Client/
  │   ├── Dockerfile/
  │   └── ...
  └── server/     
  │   ├── Dockerfile/     
  │   └── ...
  ├── docker-compose.yml

So to dockerize a react app is quite easy. The following is the dockerfile for the front end.

FROM node:16.10.0 as builder-step

WORKDIR /Client

COPY package*.json /Client/

RUN npm install 

COPY . .

RUN npm run build

RUN npm install -g serve

EXPOSE 3000

CMD ["serve", "-s", "build"]

Let us go over the configuration options;

  • FROM - installs the Nodejs image for the specified version.

  • WORKDIR - specifies the working directory of the react app.

  • COPY - copies the package.json and all the frontend files in the container.

  • RUN - executes a command inside the container which in this case installs the node_modules, builds the app and installs the serve npm package to serve the build.

  • EXPOSE - specifies the port we want the react app to be exposed on.

  • CMD - script to run the app after the image has been built.

To build the image individually we can run the following command in our react app root directory where our dokerfile resides. You can replace client with whatever name you choose to give your image.

docker build -t client .

To run the image, we can do so with the following command.

docker run client

Dockerize the backend

Next would be to dockerize the backend as we have done for the frontend by adding the dockerfile to the root directory of our server.

FROM node:16-alpine

WORKDIR /Server

COPY package.json .

COPY package-lock.json .

RUN npm install 

COPY . .

EXPOSE 5000

CMD ["node", "app.js"]
  • FROM - installs the Nodejs image for the specified version.

  • WORKDIR - specifies the working directory of the express-server.

  • COPY - copies the package.json and all the server files in the container.

  • RUN - executes a command inside the container which in this case installs the dependencies.

  • EXPOSE - exposes the server to port 5000.

  • CMD - runs the script with node.

If you wish to add environment variables when building you can do so using ARG and ENV

ARG var
ENV varname=${var}

and pass it to --build-arg when building the image as so.

docker build --build-arg var=<your_variable> -t <image_name> .

Adding docker-compose to the root directory.

Docker-compose allows us to define and run multiple container applications in docker. We can do this by adding a docker-compose.yml in the root directory that enables us to run the entire application with a single command. For our MERN app, we will define three services i.e React app, express-server and MongoDB.

Note If you are using MongoDB atlas there is no reason to build a MongoDB image. However, for purposes of this tutorial, I shall include it.

version: '3'

services:

  server:
    build: 
      context: ./Server
      dockerfile: Dockerfile   
    ports:
      - "5000:5000"
    volumes:
      - ./Server:/Server
    depends_on:
      - mongodb

  client:
     build: 
       context: ./Client
       dockerfile: Dockerfile
     ports:
       - "3000:3000"
     volumes:
      - ./Client:/Client

  mongodb:
      image: "mongo"
      volumes:
        - data:/data/db
      ports:
        - "27017:27017"

volumes:
    data:
  • version: Docker Compose file format version that will be used.

  • services: individual services in isolated containers. Our application has three services: Client (React), Server (Nodejs) and MongoDB (MongoDB database).

  • build : Defines the path to our service and dockerfile.

  • volumes: volumes stores our build so that we don't have to rebuild every time we make a change and restart the service.

  • ports: maps the exposed ports from our containers to our machine ports.

To run our MERN app using a single command we'll enter the following in our terminal from the root directory.

docker-compose up -d --build

This will build the containers if they weren't built before and run them. When one runs docker-compose up it creates a network by default(if it wasn't specified) named <directory_name>_default that connects our containers. Adding the -d argument enables our application to run in the background. To stop the containers you can use docker-compose down

Conclusion

To wrap it all up, we have learned what is containerization and how to containerize a MERN application using docker. You can run the application on a server of your choice. We will see how to host our application on NGINX and integrate it with an auto-deploy tool. Hope you learned something and see you soon.