So you want to Dockerize your React app?

Written by debabrata100 | Published 2018/08/31
Tech Story Tags: javascript | react | docker | nginx | kubernetes

TLDRvia the TL;DR App

How to run your react application in docker

Cool guys ! It takes two minutes to put your react build inside a docker container. I assume you are familiar with docker and nginx. If not, no worries.

If you are completely new to docker here are two YouTube links which makes you understand what docker is and how to use it.

Before going into this tutorial, let me introduce what docker is. Docker enables developers to easily pack, ship, and run any application as a lightweight, portable, self-sufficient container, which can run virtually anywhere. Docker containers are easy to deploy in a cloud.

The code base for this tutorial: https://github.com/debabrata100/react-docker

Here are the steps we will follow to achieve this.

  1. Set up docker image
  2. Deploy your application
  3. Issues you might face in the production environment

NOTE: This tutorial is based on create-react-app

Set up docker image:

There are two ways you can create your docker image.

case-1: You want to build your react application within docker container

case-2: You build your react application in local and push the build artifacts into docker

So much talk. Lets get to action.

case-1:

Create a file called Dockerfile in your project root and add the following code

# stage: 1
FROM node:8 as react-build
WORKDIR /app
COPY . ./
RUN yarn
RUN yarn build

Explanation:

  • In the first line we are using official node image version 8, you can specify any other version specific to your project.
  • We are using react-build which allows us name the first stage of build process which we will be using in stage 2 in production environment
  • In line 2 we are using our working directory as app
  • In line 3 we copy everything into app directory.
  • In line 4 and 5 we are installing all the dependencies from package.json and building our app which creates the build artifacts inside /app/build

You must notice at line 3, we are copying everything into docker image and you think there is a problem here. Yes there is a problem, because this will copy everything including your node_modules into docker image, So we must use a .dockerignore file to get rid of this issue.

I used yarn instead of npm, because yarn is faster than npm as you know when installing dependencies for a project, npm installs packages sequentially. This slows down the performance significantly. Yarn solves this problem by installing these packages in parallel.

Now create a .dockerignore file inside your root directory and put these following lines of code into it.

.git
node_modules
build

If you have more files to ignore, you can mention inside .dockerignore file.

Now let’s go to stage-2. In this step we will copy our build artifacts and put into a server where our application will run. Put the following code into your Dockerfile.

# stage: 2 — the production environment
FROM nginx:alpine
COPY — from=react-build /app/build /usr/share/nginx/html
EXPOSE 80
CMD [“nginx”, “-g”, “daemon off;”]

Explanation:

  • In the first line we are pulling nginx image from docker hub.
  • In the second line we are copying the build artifacts generated in the first step into nginx public directory. Remember we used react-build in the first step to help us copy the artifacts from the first step as we are using multi stage build process which is introduced from docker 17.05.
  • In line 3 we are exposing port 80 at which our application is running inside our docker container.
  • The last line is to run the server when container starts.

As of now your final Dockerfile should look like this:

<a href="https://medium.com/media/ada42ceb523e2b08afa7461d920ab888/href">https://medium.com/media/ada42ceb523e2b08afa7461d920ab888/href</a>

At line-10, I have added COPY nginx.conf /etc/nginx/conf.d/default.conf. I will explain this in issues section. Please remove this line or put a # at the front to comment it.

Your .dockerignore file should look as following

<a href="https://medium.com/media/46bc18c8106b248cb3f1681df6688384/href">https://medium.com/media/46bc18c8106b248cb3f1681df6688384/href</a>

Now we have done with our docker image configuration, its time to run our container.

Deploy your application

Lets build the docker image we just created with the following command

$ docker build . -t react-docker

I hope you build the image successfully. To see the list images built in your system, run the following command

$ docker images

Lets run container now

$ docker run -p 8000:80 react-docker

Now open http://localhost:8000 in your browser to check its running !

<a href="https://medium.com/media/901b6b701ae11087429ce2c5022d615b/href">https://medium.com/media/901b6b701ae11087429ce2c5022d615b/href</a>

To check running containers run $ docker ps

To dive into your docker container run $ docker exec <container_id> sh

The deployment process we have discussed here works only in your local environment, If you are facing any issue with deployment in production environment using kubernetes , please comment here I can help you out .

case: 2

In case: 1 we have discussed how to build and deploy the complete application inside docker. Many developers choose not build the application inside docker since the above process creates two docker containers hence increase space in your disk though you can write commands to delete the node_modules generated in stage-1 which takes more space. Now lets choose another way of deploying.

Run

$ yarn build. // This will generate the build artifacts in local.

Now in your Dockerfile, Write only stage-2 commands as below

FROM nginx:alpine
COPY /build /usr/share/nginx/html
EXPOSE 80
CMD [“nginx”, “-g”, “daemon off;”]

Now before building the docker image, make sure you exclude build folder from your .dockerignore file

Now run

$ docker build . -t react-docker

This should build your docker image successfully.

To check that, run $ docker images

Now run $ docker run -p 8000:80 react-docker and navigate to http://localhost:8000

You should be able to run your application in browser successfully.

Issues you might face in the production environment

If you are using react-routers, you might find the routes are not working when you refresh the particular page. Now try refreshing you page on the browser and you might get 404 page by nginx server. No worry, here is a solution from stack-overflow.

When your react.js app loads, the routes are handled on the frontend by the react-router. Say for example you are at http://a.com. Then on the page you navigate to http://a.com/b. This route change is handled in the browser itself. Now when you refresh or open the url http://a.com/b in the a new tab, the request goes to your nginx where the particular route does not exist and hence you get 404.

To avoid this, you need to load the root file(usually index.html) for all non matching routes so that nginx sends the file and the route is then handled by your react app on the browser. To do this you have to make the below change in your nginx.conf or sites-enabled appropriately

location / {
 try_files $uri /index.html;
}

This tells nginx to look for the specified $uri, if it cannot find one then it send index.html back to the browser.

So In order to implement this create a file call nginx.conf in your project root and the following code into it

<a href="https://medium.com/media/e2789738cdf24a55632513c56103d971/href">https://medium.com/media/e2789738cdf24a55632513c56103d971/href</a>

You can remove the comment lines as you wish, I copied this file from /etc/nginx/conf.d/default.conf

Now we need to overwrite this default.conf file with our nginx.conf created shortly. The only extra line added at line -10 is

try_files $uri /index.html;

We need to change our docker file to push this file into docker as I have discussed this before when I added an extra line to the dockerfile i.e

COPY nginx.conf /etc/nginx/conf.d/default.conf

<a href="https://medium.com/media/d001754c664e3f68da1a3ab6eb47009b/href">https://medium.com/media/d001754c664e3f68da1a3ab6eb47009b/href</a>

If you have skipped this line in the previous docker files in both case-1 and case-2, add it back to get rid of this issue.

Thank you :)


Written by debabrata100 | Software Developer @Charcoal Eats
Published by HackerNoon on 2018/08/31