Deploy a React App on Kubernetes using Docker

Learn how to deploy a React web app on Kubernetes using docker and kubectl

Maksood Mohiuddin
8 min readOct 26, 2020

ReactJS, an open source library by Facebook, is one of the most popular JavaScript library to build user interface. Kubernetes, originally designed by Google and is now maintained by the Cloud Native Computing Foundation, is the most popular open-source container-orchestration system for automating application deployment, scaling, and management. Docker is a PAAS (platform as a service) product that uses OS-level virtualization to deliver software in packages called containers. In this article, I will show step by step instructions to bootstrap a simple React JS app, containerize the React app using docker and deploy the container in Kubernetes.

PART I: Build the the REACT APP

We will use the following tutorial to create a simple React JS app: https://reactjs.org/docs/create-a-new-react-app.html.

By following this tutorial, you should have a simple react app running in your browser:

Simple React JS App Running in Browser

Now, lets open the generated React code in your favorite IDE/code editor (I like Visual Studio Code — it has multi platform support and free https://code.visualstudio.com)

Note: if you have an existing react app, you can skip Part I and follow along from PART II.

PART II: Containerization of the REACT APP using Docker

Now let’s containerize the react app from Part 1 using Docker. You need to have Docker running locally for this to work. If you do not have docker installed locally, please check https://www.docker.com/get-started. Docker is extensive and beyond the scope of this article but if you just complete the get started tutorial that gets prompted when you install Docker Desktop you will be good to go.

In the root of your project, add a file name Dockerfile (note this file has no extension). At this time, your project directory should look something like below:

Dockerfile

Add the following in your Dockerfile. Please see the comments to review how we are building our docker image:

# STAGE 1 - build the react app # set the base image to build from 
# This is the application image from which all other subsequent
# applications run. Alpine Linux is a security-oriented, lightweight #(~5Mb) Linux distribution.
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
# add the node_modules folder to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# copy package.json file to /app directory for installation prep
COPY ./package.json /app/
# install dependencies
RUN yarn --silent
# copy everything to /app directory
COPY . /app
# build the app
RUN yarn build
# STAGE 2 - build the final image using a nginx web server
# distribution and copy the react build files
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
# needed this to make React Router work properly
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/nginx.conf /etc/nginx/conf.d
# Expose port 80 for HTTP Traffic
EXPOSE 80
# start the nginx web server
CMD ["nginx", "-g", "daemon off;"]

Notice that we are referencing a file nginx/nginx.conf but we have not added that to our project yet. Let’s add that file now.

In the root of your project, add a folder name nginx and add a file nginx.conf. Add the following in 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;
}
}

NGINX is one of the most popular open source web server. Here we are setting up a nginx web server that listens to port 80 and serves content from the directory /usr/share/nginx/html. Part of our docker build we replace the default content generated by nginx web server with the build output of our react app. At this time, your project directory should look something like below:

nginx configuration

Now, in the project directory where the Dockerfile is located, you can run the following command to build a docker image that builds our react app, setup a nginx web server & replaces default content of nginx web server with the content from the build output from our react app and tag the image as react-docker-k8s:

docker build -t react-docker-k8s .

Then can run a docker container based on the docker image we just created:

docker run -it -p 3000:80 — rm react-docker-k8s:latest

We can ignore this warnings in the console:

10-listen-on-ipv6-by-default.sh: error: /etc/nginx/conf.d/default.conf is not a file or does not exist

This is because we have modified the default nginx configuration to make our react router work. Since we are pointing our local port 3000 to container port 80, we can open the react app by browsing to http://localhost:3000

We have a react app running as a docker container! In our terminal, we should see HTTP traffic from our browser to our container.

Before we conclude the docker part, one important concept to understand is the difference between a Docker image and a Docker container. Metaphorically, an image is a recipe whereas container is the cake. So just like you can make many cakes using the same recipe, you can have many running containers of the same image.

Finally, let’s push the docker image to our public docker hub repository. Note that, by default Kubernetes looks in the public Docker registry to find images. We can also use our local or a private image repository but for simplicity we will just use the Kubernetes default public docker hub image repository.

First, lets tag our image:

docker tag react-docker-k8s [your public docker hub name]/react-docker-k8s

Then push it our public docker hub:

docker push [your public docker hub name]/react-docker-k8s

PART III: Container Orchestration using Kubernetes

In part 2, we have launched a single container using the image we have created for our simple react app. That’s fine for our local/development/lower environment but how would we manage containers for production workloads? If required, how will we scale our react app image into hundreds (or thousands!) of containers? Enter Kubernetes. Kubernetes in essence is a container orchestration platform.

You need to have Docker and Kubernetes running locally for this part. There are various option to deploy to Kubernetes (all major public cloud provider has Kubernetes engine where you can deploy). However, one simple option is using the Kubernetes engine that comes with Docker (you need to enable it from Docker Desktop if you have not already) and that’s what we will be using here: https://www.docker.com/products/kubernetes

In the root of your project, add a file name deployment.yml and add the below content in the deployment.yml file (make sure to be consistent with the indentation as yml files are indentation sensitive).

apiVersion: apps/v1
kind: Deployment
metadata:
name: react-docker-k8s
labels:
app: react-docker-k8s
spec:
replicas: 2
selector:
matchLabels:
app: react-docker-k8s
template:
metadata:
labels:
app: react-docker-k8s
spec:
containers:
- name: react-docker-k8s
image: [your public docker hub name]/react-docker-k8s
ports:
- containerPort: 80
resources:
limits:
memory: '128Mi'
cpu: '200m'
livenessProbe:
httpGet:
path: /index.html
port: 80
initialDelaySeconds: 15
timeoutSeconds: 2
periodSeconds: 5
failureThreshold: 1
readinessProbe:
httpGet:
path: /index.html
port: 80
initialDelaySeconds: 15
periodSeconds: 5
failureThreshold: 1

Few things to note about the deployment.yml file:

- Metadata and labels can be anything but if you are building a full stack application then those becomes important as Kubernetes uses these metadata to puts various part of the applications together.

- We are creating 2 containers (replicas) from our image.

- For image, we are using the image we have created and pushed to our docker hub in Part 2.

- We are opening Port 80 of our container to outside world. Port 80 is a common industry practice to open up for web traffic and as we saw in the nginx/nginx.conf file in our repo we are listening to Port 80.

- You can use the resources as specific here for development purpose but if you are using this for production you may like to to revisit this as needed.

- livenessProbe and readinessProbe are part of Kubernetes auto healing mechanism and one of many reasons that makes Kubernetes a great container orchestration tool. Kubernetes uses liveness probes to know when it needs to restart a container. Kubernetes uses readinessProbe to allow applications to have extra time to get ready to be added to the cluster e.g. an application might need to load large data or configuration files during startup, or depend on external services after startup.

Please read more about the deployment here:

At this time, your project directory should look something like below:

Final project directory structure

Now, we can use this deployment file with the Kubernetes CLI (kubectl) to deploy our Containers into a Kubernetes cluster. Navigate to the location where deployment.yml and run:

kubectl apply -f deployment.yml

Verify that the deployment was successful:

kubectl get deployment

We should see react-docker-k8s as a deployed app.

Verify the pods with react app container images are running:

kubectl get pods

We should see 2 pods, named react-docker-k8s-xxxxxxxxxx-xxxxx.

Verify a pod (out of 2) with the react app image is configured & deployed correctly:

kubectl describe pod react-docker-k8s-xxxxxxxxxx-xxxxx

We can also ssh into the container pod to verify the contents of the react app:

kubectl exec react-docker-k8s-xxxxxxxxxx-xxxxx -it sh

Navigate to /usr/share/nginx/html folder where we should see the contents from build folder of our react app (from PART 1).

Verify the REACT app is working correctly: (Note: here we are simply binding a port from our local machine port 8080 to container port 80)

kubectl port-forward deployment/react-docker-k8s 8080:80

Open http://localhost:8080 to view the React app in the browser. It should be same as PART 1.

Finally, let’s try out the auto-healing feature of Kubernetes.

Lets delete one of the pod from our cluster (out of 2 we have created using our deployment.yml file):

kubectl delete pod/react-docker-k8s-xxxxxxxxxx-xxxxx

Now, watch how Kubernetes auto-heal our cluster and automatically build a POD to replace the deleted POD!

kubectl get deployment --watch

With this, we have successfully build a react app, containerized the react app using docker and deployed the docker image into a Kubernetes cluster in our local machine!

The source code for this article is located here: https://github.com/maksoodmohiuddin/react-docker-k8s

--

--

Maksood Mohiuddin

Enterprise Architect | Engineering Leader | Cloud Native & Digital Transformation Expert | Hacker | World Citizen | Father | Husband | MBA@UGA | OMSCS@GT