DEV Community

Cover image for Dockerizing React Apps
Bruno Paulino
Bruno Paulino

Posted on • Updated on • Originally published at bpaulino.com

Dockerizing React Apps

While creating ReactJS apps, you probably don't have to think too much about how to deploy them. ReactJS applications can be easily bundled in a folder, consisting of plain HTML, CSS and Javascript files. That should be simple enough to upload it to a S3 Bucket, host it on Github Pages or even integrating great services like Netlify or Zeit for fast and automated deployments.

But this week, I had the task of deploying a React app created with create-react-app on a VPS under a subdomain. I didn't want to use stone-age FTP, I wanted to have an automated docker container with my app where I could deploy anywhere without much configuration.

I created a demo app with all the configurations detailed on this post. The code is available here

Preparing our Dockerfile

We start out by creating a Dockerfile on our project root folder with the following content:

# This image won't be shipped with our final container
# we only use it to compile our app.
FROM node:12.2.0-alpine as build
ENV PATH /app/node_modules/.bin:$PATH
WORKDIR /app
COPY . /app
RUN npm install
RUN npm run build

# production image using nginx and including our
# compiled app only. This is called multi-stage builds
FROM nginx:1.16.0-alpine
COPY --from=build /app/build /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/nginx.conf /etc/nginx/conf.d
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

On the snippet of code above, we are using a feature called multi-stage builds. It requires Docker 17.05 or higher, but the benefit of this feature is enormous, which I will explain next. On the first half of the script, we are building a Docker image based on node:12.2.0-alpine which is a very tiny linux image with node included. Now notice the as build at the end of the first line. This creates an intermediary image with our dependencies that can be thrown away after build. Soon after that, we install all the dependencies from my React app with npm install and later we execute npm run build to compile the React app optimized for production.

On the second half of the code, we create a new Docker image based on nginx:1.16.0-alpine which is also a tiny linux including nginx, a high performance web server to serve our React app. We use the command COPY to extract the content from our previous image called build and copy it into /usr/share/nginx/html. Next, we remove the default nginx configuration file and add our custom configuration under nginx/nginx.conf with the following content:

# To support react-router, we must configure nginx
# to route the user to the index.html file for all initial requests
# e.g. landing on /users/1 should render index.html
# then React takes care of mouting the correct routes
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;
  }

}
Enter fullscreen mode Exit fullscreen mode

This configuration is very important for apps using React Router. Whenever you share a link to your React app, lets say, a link to /users/1/profile, this link tells the browser to request this path from the web server. If the web server is not configured properly, our React app won't be able to render the initial index.html file containing our React application.

Using our custom configuration, we tell nginx to route all requests to the root folder /usr/share/nginx/html which is the directory we previously copied our React app during image build. We should not forget that React apps are Single Page Applications, which means that there is only one page to be rendered on the first request, the rest of the job is taken care by React on the browser.

Building our Docker Image

We already have all the required code to build our Docker image. Lets execute the Docker command to build it:

# Make sure to be on the same folder of your React app
# replace 'my-react-app' with whatever name you find appropriate
# this is the image tag you will push to your Docker registry
docker build -t my-react-app .
Enter fullscreen mode Exit fullscreen mode

When the image is built, lets check the size of the image we just generated with the following command:

# List all the images on your machine
docker images
# You should see something like this:
REPOSITORY     TAG       IMAGE ID        CREATED          SIZE
my-react-app   latest    c35c322d4c37    20 seconds ago   22.5MB
Enter fullscreen mode Exit fullscreen mode

Alright, our Docker image is ready to go on to a Docker Registry somewhere. One interesting thing about this image is that the size is only 22.5MB. This is really great for deployment because small images make automated pipelines run much faster during download, image building and upload.

Running our React app with docker-compose

What we need now is a way to run this Docker image. For testing it locally, lets create a file called docker-compose.yml with the following content:

version: '3.7'

services:
  my_react_app:
    build:
      context: .
    ports:
      - '8000:80'
Enter fullscreen mode Exit fullscreen mode

Docker Compose will take care of building the image in case it doesn't exist and also bind the port 8000 from our local machine to the port 80 on the container.

Lets spin up our container with the following command:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

Now open your browser on localhost:8000 and check if our React app is running there. You should see something like this:

React JS App running on Docker

Conclusion

Running a React app with Docker might not be the best deployment, but if you need to run docker like in my case, it can be very simple and effective. This opens the door for a lot of automation pipelines you can hook up on the project like Github Actions or Gitlab CI/CD to automate your deployment process. You can find the code of this post here.

Top comments (8)

Collapse
 
bpaulino0 profile image
Bruno Paulino

That is interesting to hear.
I run it for local development on Mac as well, but using Docker Desktop and it has been great so far. How much RAM do you have? I have 16GB which helps quite a lot.
What is the difference of using Hyperkit? I have never used it.

Collapse
 
karthikeyan676 profile image
Karthikeyan

I don't know how efficient the solution is? If I use S3 for react hosting means it's a serverless right? what if I used docker so, there will be a server running all the time. The server cost will be increased. Correct me if I'm wrong.

Collapse
 
bpaulino0 profile image
Bruno Paulino

You are absolutely correct. This technique only make sense if you have a server that is already running and is not taking up too much resources, so you can just "reuse" it.
I would definitely go with S3 as my first option, like I briefly mentioned on the first paragraph.
The server costs would depend on the server provider, but a S3 Bucket is a lot cheaper.

 
mbrtn profile image
Ruslan Shashkov

8GB is too small for modern OS, especially for macOSX. With 16GB Docker works perfectly.

Thread Thread
 
bpaulino0 profile image
Bruno Paulino

That is a good point. I believe that this is a hard requirement based on the applications you are writing and IDEs you are using. For simple Node.js Apps using an editor like VS Code, 8GB should be enough. On the other hand, JVM based apps would require a bit more memory for a smooth development environment.

Collapse
 
blueboy6 profile image
BlueBoy6

Hi ! i'm realy grateful for your post, since some days i was looking for a realy simple and clear tutorial to dockerize a React application ! Thank you a lot ! 🤘

Collapse
 
bpaulino0 profile image
Bruno Paulino

Hi there, I'm glad this post helped you :)

 
bpaulino0 profile image
Bruno Paulino • Edited

I know about the virtualization part Docker does to be able to run on a Mac, but so far it is "okay" to run it on my projects. I guess you have a different use case. Perhaps larger applications running simultaneously?
Does it happen with all your apps or is it specific to certain stack running on Docker? e.g. Java projects are known to be larger and maybe more resource intensive than node apps.