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
TL;DR:
- The github repo -> https://github.com/machariamuguku/Docker-React-Nginx
- 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:
- The laptop is the software you want to deploy. In this case it’s the react front-end
- 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.
- The clearing agent is the docker-compose file. They read the instructions from the catalog (dockerfile) and execute them (deliver)
- 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.
- The carton is the base image the docker image is build on
- 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)
- The shipping container is the whole docker volume which may contain other docker containers (services?) inside.
- The ship is a container manager (eg. kubernetes or docker swarm)
- The other containers are other docker images inside the ship (container manager)
- The ships captain is nginx. They direct the containers (serve) to the correct delivery location (users)
Technical explanation:
- What is React?
2. What is docker?
3. What is nginx?
Create a react project:
0. install nodejs and yarn package manager
- 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 😊)
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
- Open the react app in an editor. For example vscode
code . #open current directory in vscode
2. Create a dockerfile
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.confmkdir 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:
- write a script to run and stop the containers
- Do load balancing on the containers and nginx
- Sell all your earthly belonging and donate to the poor