Dockerizing A React Application (with docker and nginx)

Containerization on the front-end with React and docker in development and production, and deployment of a React application with docker and nginx

Macharia Muguku
The Startup

--

Deploy react app with docker and nginx. Image rights https://www.hkinfosoft.com

TL;DR:

  1. The github repo -> https://github.com/machariamuguku/Docker-React-Nginx
  2. Steps:

a) Create a react app

b) Install docker and docker compose

c) Create a dockerfile (with a node image for building and an nginx image for deployment)

d) Create docker compose file

e) deploy the container

The Analogy:

Imagine you want to import a laptop¹ from china. You order the laptop online, you are assigned an order tracking number, they package it inside a carton, label the carton with the order number, write it’s details in a catalog², an then pack it alongside other ordered items inside a shipping container. The container is then loaded on a ship containing other containers and then delivered to your country’s port by the ships captain. You then instruct your clearing agent³ to get you⁴ the product labeled with your order tracking number. They read the product catalog and get you your laptop.

The explanation:

  1. The laptop is the software you want to deploy. In this case it’s the react front-end
  2. The catalog is the dockerfile. It contains the items arrangement inside the container and instructions for the clearing agent on how to distribute them. In a dockerfile the instructions are about how to build and deploy the application.
  3. The clearing agent is the docker-compose file. They read the instructions from the catalog (dockerfile) and execute them (deliver)
  4. You are the developer. You issue commands to the clearing agent (docker compose) telling them to read the catalog (dockerfile) and get you your product (deploy/build). In some cases you can write a script to run these commands on your behalf.
  5. The carton is the base image the docker image is build on
  6. An order tracking number is the containers name. It is used by the catalog (docker compose) to identify the appropriate carton (docker image) to deliver (deploy/build)
  7. The shipping container is the whole docker volume which may contain other docker containers (services?) inside.
  8. The ship is a container manager (eg. kubernetes or docker swarm)
  9. The other containers are other docker images inside the ship (container manager)
  10. The ships captain is nginx. They direct the containers (serve) to the correct delivery location (users)

Create a react project:

0. install nodejs and yarn package manager

  1. install the create react app cli globally
sudo yarn global add create-react-app

2. Create a new react project called react-docker-project

create-react-app react-docker-project

3. Start the project

cd react-docker-project #enter the project directory
yarn start #start the project in development mode

4. You’ll land on the default Create react app (which to my surprise changed the spinning svg logo btw 😊)

create react app landing page

Setup Docker for development:

0. install docker and docker-compose

a) install docker. Use this digital ocean tutorial to install for ubuntu.

b) install docker compose. Use the release page on github

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose

  1. Open the react app in an editor. For example vscode
code . #open current directory in vscode

2. Create a dockerfile

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.

touch dockerfile #note the absence of an extension 

3. Write the following commands inside the dockerfile. The lines starting with ‘#’ are comments. N/b docker file commands are run sequentially.

# set the base image
# This is the application image from which
# all other subsequent applications run
# why alpine? Alpine Linux is a security-oriented, lightweight
# Linux distribution. how small? how about 5Mb?
# in comparison ubuntu 18.04 is about 1.8Gb
FROM node:alpine
# set working directory
# this is the working folder in the container
# from which the app will be running from
WORKDIR /app
# copy package.json and yarn.lock
# package.json to install the packages from
# and yarn.lock for a package called chokidar
# which is used for hot reloading
COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock
# since we are using local files and not copying them to docker
# add the container's node_modules folder to docker's $PATH
# so that it can find and watch it's dependencies
ENV PATH /app/node_modules/.bin:$PATH
# install and cache dependencies
# n/b: these dependencies are installed inside docker
# it runs the command "yarn" which is an equivalent of "yarn add"
RUN yarn
# start the container
CMD ["yarn", "start"]

4. To spin up the container you can use docker run which you can read about here -> https://docs.docker.com/engine/reference/commandline/run/ or you can use the handy docker-compose as we will

a) create a docker-compose file

touch docker-compose.yml #notice the .yml extension 

b) Write the following commands inside the docker-compose file.

N/b: this file is indentation sensitive. Like a certain weird language we all know (I’m looking at you python 👀). Get the exact indentation from the github repo on the TL;DR above

#the docker compose file version
version: "3.7"
# you can run multiple services inside one docker compose file
# define them with their dependencies one after the other
services:
# service 1 named react-dev
react-dev:
# service 1 container name
container_name: react-dev
build:
# the context (working directory) is the current directory
# change this to the directory containing the dockerfile if in a different place
context: .
# the dockerfile to be run
dockerfile: Dockerfile
# map the exposed port from the underlying service to a port exposed to the outside
# in this case map port 3000 exposed by create react app to also 3000
# to be used to access the container from the outside
ports:
- "3000:3000"
# the mounted volumes (folders which are outside docker but being used by docker)
volumes:
- ".:/app"
- "/app/node_modules"
# set the environment to development
environment:
- NODE_ENV=development

c) Fire up the container. The up flag starts the container, the — build flag builds the container. N/b: you need to stop anything else running on port 3000

docker-compose up --build

d) To stop the container run . The down flag stops and removes stopped containers and delete any associated network. The — rmi flag removes the images

docker-compose down -v --rmi local

Setup Docker for production:

1. Create production dockerfile

touch Dockerfile-prod

2. Write the following commands inside the dockerfile-prod.

# set the base image
# n/b: for production, node is only used for building
# the static Html and javascript files
# as react creates static html and js files after build
# these are what will be served by nginx
# use alias build to be easier to refer this container elsewhere
# e.g inside nginx container
FROM node:alpine as build
# set working directory
# this is the working folder in the container
# from which the app will be running from
WORKDIR /app
# copy everything to /app directory
# as opposed to on dev, in prod everything is copied to docker
COPY . /app
# add the node_modules folder to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install and cache dependencies
RUN yarn
#build the project for production
RUN yarn build
# set up production environment
# the base image for this is an alpine based nginx image
FROM nginx:alpine
# copy the build folder from react to the root of nginx (www)
COPY --from=build /app/build /usr/share/nginx/html
# --------- only for those using react router ----------
# if you are using react router
# you need to overwrite the default nginx configurations
# remove default nginx configuration file
RUN rm /etc/nginx/conf.d/default.conf
# replace with custom one
COPY nginx/nginx.conf /etc/nginx/conf.d
# --------- /only for those using react router ----------
# expose port 80 to the outer world
EXPOSE 80
# start nginx
CMD ["nginx", "-g", "daemon off;"]

3. For those using react router create an nginx folder with an nginx file inside at the root of the project like so

└── nginx
└── nginx.conf
mkdir nginx #create an nginx directory
cd nginx #enter the directory
touch nginx.conf #create an nginx.conf file
cd .. #leave the directory

4. Write the following nginx configurations inside the nginx.conf file

server {listen 80;location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;location = /50x.html {
root /usr/share/nginx/html;
}
}

5. a) create a docker-compose-prod file

touch docker-compose-prod.yml

6. Write the following commands inside the docker-compose-prod file

#the docker compose file version
version: "3.7"
# you can run multiple services inside one docker compose file
# define them with their dependencies one after the other
services:
# service 1 named react-prod
react-prod:
# service 1 container name
container_name: react-prod
build:
# the context (working directory) is the current directory
# change this to the directory containing the dockerfile if in a different place
context: .
# the dockerfile to be run
dockerfile:
Dockerfile-prod
# map the exposed port from the underlying service to a port exposed to the outside
# in this case map port 80 exposed by nginx to port 3000 on the outside
# to be used to access the container from the outside
ports:
- "3000:80"

7. Fire up the container. The -f flag let’s you pick a specific docker-compose file to run, the up flag starts the container, the -d flag runs the container in detach mode (no interactive cli) and the — build flag rebuilds the container. N/b: you need to stop anything else running on port 3000

docker-compose -f docker-compose-prod.yml up --build -d

8. To stop the container run the following commands. The down flag stops and removes stopped containers and delete any associated network. The — rmi flag removes the images locally

docker-compose -f docker-compose-prod.yml down -v --rmi local

Advance Homework:

  1. write a script to run and stop the containers
  2. Do load balancing on the containers and nginx
  3. Sell all your earthly belonging and donate to the poor

--

--

Macharia Muguku
The Startup

A student of the world ; My brain has too many tabs open ; 🇰🇪; www.muguku.co.ke