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) andMongoDB(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.
