loading...

Running React and Node.js in one shot with Docker!

numtostr profile image Vikas Raj Updated on ・3 min read

This is the second part of my previous post. If you haven't read my first post, please check it out as it serves as a base for this part.

TL;DR | If you don't know docker you can just turn back. But, If you still want to read go ahead.

In this post we'll be looking at the docker way of running React and Node.js. This is a kind of advance development setup and I hope you already installed and know the basics of docker and docker-compose. If you want to know more about docker head over to docker.com

# Initial setup

$ mkdir awesome_project

In this approach we'll not polluting the root folder. Client and Server will stay on their dedicated folder. In this way we can separate client and server at any time if we have to, without breaking anything. To make it work properly, each of them should have a Dockerfile and all will be connected with the docker-compose.

# Client Setup (React)

~ Create react app

$ cd awesome_project && npx create-react-app client

This will create a folder named client which holds our react app.

~ Dockerfile for React

Create a file name Dockerfile in the client folder and paste the following code.

FROM node:lts-slim

RUN mkdir -p /usr/src/app

WORKDIR /usr/src/app

EXPOSE 3000

CMD [ "npm", "start" ]

This will be our docker image instructions for our react app where our react app will get compiled and run.

# Server Setup (Node.js)

Our server code will stay in a folder named server in the root folder. Here you can use express or any other framework of your choice to make up the server. Or you can use this sample to quickly setup a server.

~ Dockerfile for Node Server

Create a Dockerfile in the server folder. And make sure you have a dev script in you package.json. If you have different script for running your server, you can change the CMD instruction in the Dockerfile below.

FROM node:lts-slim

RUN mkdir -p /usr/src/app

WORKDIR /usr/src/app

EXPOSE 5000

# You can change this
CMD [ "npm", "run", "dev" ]

# Running with docker-compose

Docker-compose helps us to combine and run mutiple Dockerfile into a single network container. Make a file named docker-compose.yml in the root of the project and copy the following code.

version: "3"
services:
    frontend:
        container_name: awesome_web
        build:
            context: ./client
            dockerfile: Dockerfile
        image: vikasraj/awesome_web
        ports:
            - "3000:3000"
        volumes:
            - ./client:/usr/src/app
    backend:
        container_name: awesome_server
        build:
            context: ./server
            dockerfile: Dockerfile
        image: vikasraj/awesome_server
        ports:
            - "5000:5000"
        volumes:
            - ./server:/usr/src/app

Finally, we'll have a folder structure somewhat like this.

> awesome_project
    > client # This is our react front-end
        > node_modules
        > src
        - Dockerfile
        - package.json
    > server # This is our Node.js server
        > node_modules
        - index.js
        - Dockerfile
        - package.json
    - docker-compose.yml

Lastly, you need to change the proxy field in the client/package.json like

{
     "proxy" : "http://backend:5000"
}

backend is the name of our backend service in the compose file

Now we can run our project by running following command. This will create docker images and volumes which will run in the containers.

$ docker-compose up

If you want to build your images before starting your containers.

$ docker-compose up --build

This can be a tedious approach to work with as you must have the knowledge of docker and docker-compose. But It has some advantage:

  • One setup for all development workflow.
  • Docker can be used for any programming language.
  • Production Deloyment can be a breeze, if you use docker in your DevOps.
  • No npm package required (though replaced by docker).

Posted on by:

numtostr profile

Vikas Raj

@numtostr

Full Stack Engineer who is always lazy to find big solution. DevOps is in the pipeline. Loves guitar 🎸.

Discussion

pic
Editor guide
 

Hi Vikas,

Thanks for this! Was much needed. The docker images are running independently on ports 3000(client) and 5000(server). However, I am not able to communicate from the server in the client.
I have added a proxy:"http:localhost:5000" in the client package.json, still no response. How to fix this?

 

I think there is a typo. It should be http://localhost:5000

 

Yeah sorry. That's what I meant. That's what I have I have put in the client package.json

I don't know what's the issue. If you can share the code. I can look and fix it.

Your app looks fine. Please check that the PORT variable is set 5000 as you are also loading variables from .env file

Yeah bro, everything seems to be in order but I am not able to make requests to the Server container. Proxy is set correctly, PORT variables are set correctly. Dunno what seems to be the issue. It's working fine during localhost without the containers. Its also deployed on Heroku where it's working fine.

What machine do you use for development?

Hey found the solution man. Instead of "proxy": "localhost:5000", I replaced localhost with the Server's Docker service name from the docker-compose file. Now it looks something like this "proxy": "cfy_server:5000". It's working.

Bro. I seriously forgot about that. Awesome you found the issue.

Thanks for the heads up. I updated the post. I am amazed that no one encountered this issue before.

I came across this article because I am looking for a semi-unrelated react/express docker-compose issue that you solved via the above eg. http://docker-compose-service_name:xxxx.

No one anywhere said I needed to use my service name instead of localhost, but you did. Thank you for your post!

PS I included for entertainment my docker-compose file because they look almost identical, yet I didn't even come hear for help with that file; it was already finished. I came only for the package for the proxy fix.

version: '3.8'

services:
  server:
    image: ads/node-express
    container_name: ads-server
    build:
      context: ./server
      dockerfile: Dockerfile.dev
    ports:
      - "5000:5000"
    volumes:
      - ./server:/src/app
      - /src/app/node_modules
    restart: always
  client:
    image: ads/react
    stdin_open: true
    container_name: ads-client
    build:
      context: ./client
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./client:/src/app
      - /src/app/node_modules
    restart: always
 

Hi Vikas,

Thank you for a great series of articles. This has been helpful to me since I am a beginner in React and Node, but I have some experience with Docker.

I tried the basic setup from your article. But when I make changes to the frontend, I cannot see it in the browser.

I tried to add volumes in docker-compose.yml but when I start using docker-compose up --build it says that dependencies are not installed.

 

Oh, I get it. Before doing compose you need to install dependecies in both i.e. client and server project.

Because in Dockerfile there is nothing related to installation. All the magic happens in the compose file. It mounts the local directory inside the container will all the packages i.e. node_modules.

I hope this helps. 🤟

 

Thank you for the reply. Does this mean I have to install npm in local host machine?
Would there be a way to bypass this if that is the case?

Yes, you need install on the local machine.

If you don't wanna do this. Then you have to modify the Dockerfile to add the installation process inside the container.

 

Hi Vikas,

Thank you for this excellent writeup. I have a question, I built a React app using Express/Mongo/React/Node and I am using 'concurrently'. As you said, I have a messy root folder because my Express server.js file is in the root of my project and the React app resides in the 'client' folder inside the root. I would like to Dockerize this project using your approach which will separate everything and allow me to use the docker-compose.yml to combine everything. I am new to Docker and I was wondering what is the best method for me to start going about Dockerizing this project since it is ready to be deployed in its current state. It seems as though you have quite a bit of knowledge with this procedure.

Thanks in advance!

Stephen

 

If you don't know docker then don't introduce any docker stuff in your app. If ain't broke, don't fix it 😊. You should first learn good amount of docker. Bcz sometimes docker can be your enemy.

If you know docker then,

First step would be to strip out any concurrently stuff.

Optional but if you want a nice folder structure then you should look at yarn workspaces

If your app is open source I would be very happy to contribute 🤞🏻.

And I promise, learning docker will be a big plus for you.

 

Thank you very much for your reply Vikas! It is a personal project and I am just launching an early alpha to test functionality, so that is why I was considering dockerizing it. Both to force me to learn docker and also to help in the deployment. I will probably do as you said and not use it for this project and I will wait for another project and then use docker from the beginning.

Thank you very much for your input!

Stephen

It is really nice thing that you want to force yourcelf to learn docker. I really appreciate that.

And you can definitely dockerize your current app. You just have learn enough to get yourself a start. And make mistake.

Then you can later improvize on that learnings and mistakes. It the same how i did it.

 

Thanks you very much for this :). one question tho, Currently I've been updating my react app and still doing docker-compose down and up for it to apply changes. How will docker compose up automatically read the changes I've made in my react app? Thank you again! :)

 

You don't have to do compose up and down every time you make a chnage. Containers will automatically detect changes because of attached volumes in docker-compose.yml

 

thanks man.

adding that proxy to package.json saved my day

that proxy is necessary because, react and express will be running on different ips in docker engine, right?

 

what if I wanted to add a database? how would I dockerize that? sorry. im noob. but thank you for this though. really helps. :)

 

docker-compose will look like this with database. Here i am using mongo.

version: "3"
services:
    database:
        container_name: awesome_database
        image: mongo
        ports:
            - 27017:27017
        volumes:
            - /data/db:/data/db
    frontend:
        container_name: awesome_web
        build:
            context: ./client
            dockerfile: Dockerfile
        image: vikasraj/awesome_web
        ports:
            - "3000:3000"
        volumes:
            - ./client:/usr/src/app
    backend:
        container_name: awesome_server
        build:
            context: ./server
            dockerfile: Dockerfile
        image: vikasraj/awesome_server
        ports:
            - "5000:5000"
        volumes:
            - ./server:/usr/src/app
        depends_on:
            - database

And make sure to change the mongo environment variable when connecting your nodejs app with the database. The database in the connection string represent the service name of your database in the docker-compose.yml.

MONGODB_URI=mongodb://database:27017/my-db

I hope this helps you.

 

thank you my dude. much love on this one. last question though, lets say the database is mysql? is it the same setup as nosql? and the folder for db is level with client and server? thanks man. appreciate the help.

I dont know know much about SQL. You can go to the dockerhub and search for mySql image. You can read more there.

 

What if i want Backend by Golang instead of node.js ? what will be the changes ?

 

Sadly, I don't work with golang. Just search through Golang image repo on docker hub. I am sure you will find something.

 

Not a issue, i have found way to connect golang and reactjs using rest api, it's my project, i will post it here soon :)

 

I like this template and the use of volumes: instead of COPY in the Dockerfile. How would you express as the server?

 

This template is unopinionated about server framework. You just need to expose a connection port in this case it is 5000. You can use use any framework of your choice whether it is Express or GraphQl.