DEV Community

Arsh Sharma
Arsh Sharma

Posted on • Edited on

Dockerizing The MERN Stack (Without Docker Compose)

This is the fifth post in my Demystifying Docker series. I'll be showing you how you can use Docker when working with the MERN stack. You will need to know the basics of containers, volumes, and networking with Docker in order to understand this post. So if you're not aware of them I highly recommend you to go check the other posts out. With that out of the way let's get started.

I'll be showing you the Dockerfiles you'll need to write and the commands you'll be using to spin up your containers and explaining them step by step in the following order:

  1. Database
  2. Backend
  3. Frontend

Do note that this will be a development setup with Docker and not a production setup because we will be configuring stuff in a way that provides us much needed development features like live updating of our app when changes are made to the code. I will be covering how to set up Docker for production and deployment in a future post.

Let's begin!

Initial Setup

Before we start with anything let us create a network that will help our containers interact with each other easily.

docker network create mern-net
Enter fullscreen mode Exit fullscreen mode

Database

We won't be needing a Dockerfile for MongoDB since we'll be grabbing the official image from Docker Hub. This official image also gives us two optional environment variables which we can pass to restrict access to our database. Those being MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD.

Whenever some container would want to connect to this running MongoDB container it will have to use the username and password we specify here. Apart from that, we will also set up a named volume so that even if you remove this MongoDB container and start a new one, our previously stored data is not lost. Taking all this into consideration our final command for starting the container would be:

docker run --name mongodb -v data:/data/db --rm -d --network mern-net -e MONGO_INITDB_ROOT_USERNAME=arsh -e MONGO_INITDB_ROOT_PASSWORD=pass123 mongo
Enter fullscreen mode Exit fullscreen mode

Here,

  1. mongo is the name of the image we grab from Docker Hub
  2. mongodb is the name we give to this container
  3. /data/db is the path where data is stored inside this container.
  4. --rm flag tells Docker to remove the container if we stop it.
  5. -d flag tells Docker to run the container in detached mode.

Backend

For the backend we will need a Dockerfile which will look like this:

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 80

ENV MONGODB_USERNAME=root
ENV MONGODB_PASSWORD=pass

CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

For our changes to be reflected automatically in the containerized backend we will also need to add nodemon as a dependency and create a start script. So the package.json would look something like this:

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app.js"
  },
 "devDependencies": {
    "nodemon": "^2.0.4"
  }
Enter fullscreen mode Exit fullscreen mode

Inside our code, we would also have to change the Docker connection string to specify the username and password from the environment variables. The connection string should look something like this:

mongodb://${process.env.MONGODB_USERNAME}:${process.env.MONGODB_PASSWORD}@mongodb:27017/mern-project?authSource=admin
Enter fullscreen mode Exit fullscreen mode

Do remember to add the ?authSource=admin at the end of the string if it's not present already.

And our final command to run the container would be:

docker run --name mern-backend -v /home/arsh/docker-examples/mern-app/backend:/app -v /app/node_modules -e MONGODB_USERNAME=arsh -e MONGODB_PASSWORD=pass123 -d --rm -p 80:80 --network mern-net mern-node:latest
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. We used a bind mount to make a link with our code inside the container so that the latest changes in code are picked up automatically. We also used an anonymous volume so that while using the bind mount our node_modules folder doesn't get lost. (explained in detail in the third post)

  2. We need to pass the environment variables MONGODB_USERNAME and MONGODB_PASSWORD, the default values for which we specified in the Dockerfile as root and pass respectively. These will be plugged into the MongoDB connection string present in our code.

  3. We published the post 80 (on which our app runs) as this will be used by the frontend to interact with the backend. We won't be using our docker network for a connection between frontend and backed so we need to publish the port.

  4. We need to use our database container name (mongodb) in the connection string and specify the mern-network while starting this container so that our backend can interact with the database.

  5. mern-node:latest should be replaced with whatever you named your image while building it from the Dockerfile.

Frontend

For the frontend our Dockerfile should look like this:

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]
Enter fullscreen mode Exit fullscreen mode

And we will run the following command to start this:

docker run -v /home/arsh/docker-examples/mern-app/frontend/src:/app/src --name mern-frontend --rm -p 3000:3000 -it mern-react
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. We use a bind mount so that changes in our code are reflected automatically in the running frontend container.

  2. We publish the port 3000 and will use it to interact with our app from the browser. We don't add the frontend to our mern-network since the code for it gets executed in the browser. It wouldn't be possible to interact via the localhost if we add it to our docker network. And since it is not part of the docker network, we published the port 80 while starting the backend container.

  3. It is a requirement that the frontend container be started in interactive mode, so we also use the -it flag. This has to do with the way create-react-app works.

  4. mern-react can be replaced with whatever you named your image while building it from the Dockerfile.

Conclusion

With this, we are done dockerizing our MERN app. Visit https://localhost:3000 to interact with your functioning dockerized code!

Thanks for reading :D

If you have any feedback for me or just want to talk feel free to connect with me on Twitter. I'll be more than happy to help you out! :)

Top comments (0)