Containerizing a Node.js Express + React App with Docker
By: Dylan Edwards
Tags: Docker, Software Engineering, Expressjs, JavaScript, React

In this tutorial, I am going to be taking you through the process of containerizing a Node.js Express backend and React frontend. We will be containerizing the sentiment analysis app, a project that I have been working on with Dan Murphy that aims to provide users with sentiment analysis of their tweets through NLP. This is the GitHub repo for the project if you wish to follow along: https://github.com/dylane1999/SentimentAnalysisApp.

Docker is a set of platform as a service products that use OS-level virtualization to deliver software in packages called containers. Containers are isolated from one another and bundle their own software, libraries and configuration files; they can communicate with each other through well-defined channels.

The process of ‘Dockerizing’ any application begins by creating a Docker Image for each of the sections of the app. In this example, we will be creating an image for the express server first, and then the React app.

A Docker image is a read-only template with instructions for creating a Docker container. It provides a convenient way to package up applications and preconfigured server environments, which you can use for your own private use or share publicly with other Docker users.

Creating the backend image

we will start the image for the backend by creating a Dockerfile inside of the Backend directory

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

the Dockerfile is used with the docker build command to create the image from which our express server can be run

inside of the Backend’s Dockerfile add the following code

FROM node:12.18.3

#specify version of node that was used in the projects development LABEL version="1.0"

LABEL description="This is the base docker image for the Tweet Sentiment Analysis backend API."

LABEL maintainer = ["<yourEmail@email.com>"] WORKDIR /app

# create a directory for the image filesystem COPY ["package.json", "package-lock.json", "./"]

#copy into image file system RUN npm install --production

# Run the command inside your image filesystem. COPY . .

# Copy the rest of your app's source code from your host to your image filesystem. EXPOSE 5000

#Expose port CMD ["node", "backend.js"]

# Run the specified command within the container.

create the frontend image

now we move into the frontend directory, and create another Dockerfile in the root of the frontend folder

this is very similar to the backend image because they both run using node.js, the difference is the exposing of port 3000, instead of 5000, and a different description

FROM node:12.18.3

#specify version of node that was used in the projects development LABEL version="1.0"

LABEL description="This is the base docker image for the Tweet Sentiment Analysis frontend react app."

LABEL maintainer = ["<yourEmail@email.com>"] WORKDIR /app

# create a directory for the image filesystem COPY ["package.json", "package-lock.json", "./"]

#copy into image file system RUN npm install --production

# Run the command inside your image filesystem. COPY . .

# Copy the rest of your app's source code from your host to your image filesystem. EXPOSE 3000

#Expose port CMD ["npm", "start"]

# Run the specified command within the container.

Building the image

We can build an image from the one of the Dockerfiles we created using this command:

make sure you are inside of the directory that you want to build

docker build -t image-name .

the -t tag allows us to specify the name of the image

we add “.” after naming our image to let docker know that the directory we want to build from is the current one we are inside of

now that we have created the image, we can verify its success by using:

docker images

to run an image use the docker run command like so, specifying the ports desired, and the image name you want to run

docker run -p 5000:5000 -d image-name

to display current running docker images, use the command:

docker ps

Setting up for Docker Compose

building an image would be enough to deploy our app if we only had one image, but because we have 2, we need to use docker compose to allow our images to talk to each other via Http requests through the proxy middleware in the frontend

to do this, we will create a docker-compose.yml file in the root of our project

/App

/frontend

/backend

docker-compose.yml

.gitignore

inside of our docker-compose.yml we define

version — version of docker you are working with

services — the two images that we want to run

each service contains a build which consists of:

context: identifies where this build is in reference to the location of docker-compose.yml

dockerfile: identifies this images Dockerfile is in reference to docker-compose.yml

next, we reference our respective image for each build and map our apps default port to your desired port number

after define context, we can reference our image as if we were inside of the frontend or backend directory

version: '3' services:

backend:

build:

context: ./backend

dockerfile: ./Dockerfile

env_file:

"./.env"

image: "dmurphy1217/twitter-sentiment-backend"

ports:

- "5000:5000"

frontend:

build:

context: ./client

dockerfile: ./Dockerfile

image: "dmurphy1217/twitter-sentiment-frontend"

ports:

- "3000:3000"

links:

- "backend"

Note that because the frontend uses a proxy to communicate with the backend, we add a link to the frontend service so that we can connect to the backend using the specified link name.

We also add our env to the application by specifying it under env_file in the backend service.

in the frontend, we alter our proxy middleware to include this new “backend” link before the port

const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function(app) {

app.use(

'/analyze',

createProxyMiddleware({

target: `http://backend:5000`,

changeOrigin: true,

})

);

};

Composing the images

now that we have our docker-compose set up, and our frontend is ready to proxy the image, we can actually build the app

navigate into the root directory and run this command to build the docker images together as a container

docker-compose up -d

the -d, can also be used as ( — detach) tells docker to run the container in the background so that we can continue to use out terminal

to check the status of our application we can use:

docker-compose logs

when we want to stop our containers from running we can use this command to kill the process

docker-compose down

if we have changed something in our code, and want to rebuild the docker images to run again we can specify — build in our compose command