All Articles

Lets dockerize a MERN app!

Intro

As there are loads of tutorials out there that go over the specifics of what everything does. I’m going to try and keep this short sweet and simple. This assumes you have basic docker knowledge. I will be touching on any issue I had come across and what I’ve done to resolve them.

In the end, you will have a docker-compose driven MERN stack with hot reload

Folder structure

This docker app will have the following folder structure.

root-folder
  |>frontend
    Dockerfile
    {front end files}
  |>server
    Dockerfile
    {server end files}
  docker-compose.yml

Frontend

From inside the frontend folder run npx create-react-app .

This command creates a full react app for you. With various folders, files, and configurations out of the box. This is the easiest way to do it if you want to start with a skeleton project.

After this command is done run yarn start or npm start. If you are able to access and edit the site + see hot reloading. then you are good to keep moving.

While still in the frontend folder, create a file named Dockerfile and paste the following

FROM node:alpine

WORKDIR /app 

COPY package.json package.json 

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm","run", "start"]

This docker file does the following.

  1. use the node:alpine base box. alpine is a minimal node.js docker box.
  2. Set the folder we will be using to hold all our code
  3. Copy are deps list, install them, then copy code to the folder.
  4. Open port 3000 to the outside
  5. Command to run when starting the box
  6. You can check to see if everything is working by running the following commands
docker build -t react-app .
docker run -p 3000:3000 react-app

Server

For the server, we will be using a similar command to get us a skeleton project. Both the server setup and frontend setup are very similar

From inside the server folder run npx express-generator .

This command creates a full express app for you. With various folders, files, and configurations out of the box. This is the easiest way to do it if you want to start with a skeleton project.

You will need to create a .env file and put PORT=5000 for the express app to start on the correct port.

After this command is done run npm install -g nodemon to enable hot reloading.

nodemon ./bin/www to start the express app. You should then be able to access the site on localhost and ensure everything is working.

While still in the server folder, create a file named Dockerfile and paste the following

FROM node:alpine

WORKDIR /app 

COPY package.json package.json 

RUN npm install 

COPY . . 

EXPOSE 5000 

RUN npm install -g nodemon 

CMD [ "nodemon", "./bin/www" ] 

This docker file does the following.

  1. use the node:alpine base box. alpine is a minimal node.js docker box.
  2. Set the folder we will be using to hold all our code
  3. Copy are deps list, install them, then copy code to the folder.
  4. Open port 3000 to the outside
  5. install hot reloading globally
  6. Command to run when starting the box

You can check to see if everything is working by running the following commands

docker build -t myapp-server .
docker run p 5000:5000 myapp-server 

Packing everything into a single file

Now in the main folder create a docker-compose.yml and past the following.

version: '3.7'

services:
  server:
    tty: true
    build:
      context: ./server
      dockerfile: Dockerfile
    image: myapp-server
    container_name: myapp-node-server
    command: npm start
    volumes:
      - ./server:/app
      - /app/node_modules
    ports:
      - "5000:5000"
    depends_on:
      - mongo
    env_file: ./server/.env
    environment:
      - NODE_ENV=development
    networks:
      - app-network
  mongo:
    image: mongo
    volumes:
      - data-volume:/data/db
    ports:
      - "27017:27017"
    networks:
      - app-network
  client:
    tty: true
    build:
      context: ./frontend
      dockerfile: Dockerfile
    image: myapp-client
    container_name: myapp-react-client
    command: npm start
    volumes:
      - ./frontend:/app
      - /app/node_modules
    depends_on:
      - server
    ports:
      - "3000:3000"
    networks:
      - app-network
    environment:
      - CHOKIDAR_USEPOLLING=true
networks:
    app-network:
        driver: bridge

volumes:
    data-volume:
    node_modules:
    web-root:
      driver: local

This may look like a lot, be reading through the file is very self-explaining.

You can now run the file with

docker-compose build
docker-compose up

You should now be able to visit localhost:3000 and see your react app + hot reloading. Visit localhost:5000 and see your express app + hot reloading. Lastly, you should be able to connect to your mongo server on port 27017

Important notes about docker-compose.yml

While doing this myself self I ran into a problem with the react app not hot reloading.

It is very important to have

environment:
  - CHOKIDAR_USEPOLLING=true
tty: true

in the file for the client section.

There is currently a bug in react-scripts 3.4.3 the breaks building in docker if the terminal is not open.

Debugging

If you need to debug the express server, I would recommend commenting on the client section. You will then be able to see the output from the express server. This can sometimes lead you to any issues it is having.

If you keep getting module not found errors. Make sure the module is in your package.json. Make sure you rebuilt the docker-compose. If that fails try editing the Dockerfile for the app that is missing the module. adding node install -g module-name allows you to install it globally. While this is not the best option, it can be the fastest and least frustrating.

Published Apr 5, 2022

L3 software engineer