Docker Containers for React Deployment
Docker is an awesome tool for isolation and stateless deployments. Web applications are a great way to learn how to deploy using containers. In this post, I’ll use Docker containers for React deployments. This post will use multi-stage builds in Docker that allow building an application and only using what is needed in the container.
I’ll be touching on the following topics in order:
- Create a React Application from Create React App
- Explain what a multi-stage build is useful for
- Create a Dockerfile
- Build and use the React app.
Start a React Application
One of the most popular methods to get a React Application template is the Create React App tool. This will give you a structure and an example files to get started with. It was open-sourced by Facebook, which is the creator of React. If you do not have the npm package manager installed, follow the tutorial here.
The initial step is to navigate to the directory where the project will be created. Then run the following command provided by the Create React App docs. Remember to switch “my-app” with your project name. This will create a template to begin developing.
npx create-react-app my-app
Docker Multi-Stage Build
Docker build may include just one stage where the build is executed in another environment. Then the Dockerfile will use the build artifact paths to build an image. Another method is to place all the files in the container, run build commands in the image, and run it.
The problem with running the build in another environment and copying artifacts of the build to the container is that the process is scattered. It is an additional step to handle in deploying an app.
The problem with copying all the files into a container image and running the app build inside makes the image size much larger than it is intended to be. This will result in having files in the container that are not used while using the app. While pulling the container from the registry there are more contents to download and also a larger image on your host device. It uses more resources than it should.
Multi-stage builds are perfect for this, and both problems above can be addressed. One can build the app in the image build process and discard files that are not build artifacts.
How is this applied to React?
Applying this to React is very simple. I will use two images. One is the Node image using Alpine Linux and the other is Nginx which is used for reverse proxy and serving applications.
There are two steps to make this happen. Initially, we will copy all files to the Node image and run the build. Then we will copy only the build directory from the Node image to the Nginx image.
By doing this I’ll be omitting the files used for development and only have the necessary files for the production deployment. In the Node image stage, the environment is used as a “build environment”. This will run the build and save the files. The final image will be lean but have enough to run a production app.
Creating the Dockerfile
Dockerfiles are very straightforward. It explicitly tells what is needed and what has to be done. Let’s begin by creating the first Node stage.
FROM node:lts-alpine3.20 as build
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
RUN npm run build
This is quite as simple as it sounds. The first line declares the image that is used for this stage. In this case, since I have multiple stages, I added the name “build”. This will be used in the second stage when I have to reference this stage and copy the contents.
The second line shows where we will be working in the image. In this case, it will be the “/app” directory. Then we copy both the package.json and package-lock.json files and install the packages listed in package.json. After the installation is complete we copy all the files over and run the build of the React application.
This concludes the first stage. Let’s move on to the second.
FROM nginx
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Here the first line contains the image I use in the second stage called “nginx”. Then using the build stage we declared above, we copy over the directory that is the output of the build. The destination copied to is where I’ll be serving the app from, this will be used shortly. Then we copy over the configuration file as the default configuration and expose only port 80 for ingress. Finally, we run Nginx.
The second stage copies a file called “nginx.conf”. Here is how it looks.
server {
listen 80 default_server;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ index.html =404;
}
}
I will not go over what this configuration file does but essentially it will declare where the app lives and how to handle when a request comes in from port 80.
Build and Run the Docker Image
Docker has very detailed and useful documentation. Here is the link to it for details about the commands I will write out below.
To build the app, navigate to the directory of the Dockerfile and run the command below.
docker build -t my-app .
This command runs the build using the Dockerfile declared and will tag it as “my-app”. Feel free to make this more descriptive for your app.
To run the app, use the following command by exposing port 80.
docker run -p 80:80 my-app
An additional option is to give a name to the running container and change the above command as the following.
docker run -p 80:80 --name my-running-container my-app
You now have a multi-stage Docker build that serves a React application! If you liked this post, go check out my Credit Card Validator API in Go.
Thanks for reading this post!
If you’d like to contact me about anything, send feedback, or want to chat feel free to:
Send an email: andy@serra.us
Leave a Reply