How to package ReactJS with Docker and deploy it to Digital Ocean
For the purposes of this project, we’re going to use the standard Facebook Create React App as a base.
Assuming you have NodeJS version 10+ and can use the new npx feature, we’re going to scaffold out the project with:
npx create-react-app reactdocker;
cd reactdocker;
Test out the project:
npm start;
Should see it working on port 3000:
React Working On Port 3000
Stop the server with ctrl + c.
Next step is to create the ideal docker environment. Considering this is just HTML, CSS, and JavaScript, the only thing we need is an http server that can server up regular HTML. For this we’ll need NGINX. Starting fresh with alpine, let’s do this while in the repo directory:
docker run -it -p 3000:3000 -p 80:80 -v $PWD:/var/www/localhost/htdocs –name reactdocker alpine /bin/sh
While we’re in the container, let’s remove the node modules because they were installed with Mac OS and we need them to be installed with Alpine.
cd /var/www/localhost/htdocs;
# may take a few seconds
rm -rf node_modules;
We’ll need to install NodeJS and NPM for alpine:
apk add nodejs;
apk add npm;
Install the dependencies in the directory:
npm install;
Start the server:
npm start;# [Expected Output]# Compiled successfully!# You can now view reactdocker in the browser.# Local: http://localhost:3000/
# On Your Network: http://172.17.0.2:3000/# Note that the development build is not optimized.
# To create a production build, use npm run build.Same React Output But From Docker
Perfect, let’s stop the server and get it to build.
# press ctrl + c
npm run build;
We can see the files in the /build folder:
cd build;
ls -al;
Those files will need an http server to expose them on port 80, for this we’re going to add nginx:
apk add nginx;
Next we’ll need to configure the nginx configuration file to point to the build folder:
# add nano to edit the file
apk add nano;# modify the nginx default.conf file
nano /etc/nginx/conf.d/default.conf;
Here is the original file before:
File: default.conf
# This is a default site configuration which will simply return 404, preventing
# chance access to any other virtualhost.server {
listen 80 default_server;
listen [::]:80 default_server;# Everything is a 404
location / {
return 404;
}# You may need this to prevent return 404 recursion.
location = /404.html {
internal;
}
}
Here is the file modified:
File: default.conf
server {
listen 80 default_server;
listen [::]:80 default_server; location / {
root /var/www/localhost/htdocs/build; # this will make so all routes will lead to
# index.html so that react handles the routes
try_files $uri $uri/ /index.html;
}# You may need this to prevent return 404 recursion.
location = /404.html {
internal;
}
}
Create the directory to allow nginx to run on a process id:
mkdir /run/nginx;
Test the configuration file and start the server:
nginx -t;# [Expected Output]
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successfulnginx;
Now if we go to port 80 we can see build folder been served on nginx:
Nginx Serving React Build Folder
We can also see that the routing is working to point everything to react and give it responsibility to manage the routes:
Nginx Routing All Routes To React index.html
We’ve successfully created the environment so now we can create the Dockerfile to package things for deployment.
Now that we know what dependencies we need, we’ll create a Dockerfile and optimize if for deployment with the dependencies to do a build.
Before we destroy the container, let’s copy the nginx configuration file we modified to the repository folder itself. Press ctrl + p then ctrl + q to exit the container, then:
# create a new config directory
mkdir config;# copy default.conf to config/default.conf
docker cp reactdocker:/etc/nginx/conf.d/default.conf config/default.conf;# now we can destroy the container
docker rm -f reactdocker;
Let’s start with the base Dockerfile in root of the repository:
File: Dockerfile
FROM alpineEXPOSE 80ADD config/default.conf /etc/nginx/conf.d/default.confCOPY . /var/www/localhost/htdocsRUN apk add nginx &&
mkdir /run/nginx &&
apk add nodejs &&
apk add npm &&
cd /var/www/localhost/htdocs &&
rm -rf node_modules &&
npm install &&
npm run build;CMD [“/bin/sh”, “-c”, “exec nginx -g ‘daemon off;’;”]WORKDIR /var/www/localhost/htdocs
Let’s build it:
docker build . -t reactdocker
Running the container:
docker run -it -d -p 80:80 –name rdocker reactdocker;React running from Docker container image
Now our container is ready to be push to Docker Hub and ready to be deployed.
You’ll notice that the COPY takes a bit of time to complete, so we’ll add an ignore file and remove some of the dependencies that we no longer need.
File: .dockerignore
node_modules
Next we’ll modify the Dockerfile to remove the dependencies we don’t need:
File: Dockerfile
FROM alpineEXPOSE 80ADD config/default.conf /etc/nginx/conf.d/default.confCOPY . /var/www/localhost/htdocsRUN apk add nginx &&
mkdir /run/nginx &&
apk add nodejs &&
apk add npm &&
cd /var/www/localhost/htdocs &&
npm install &&
npm run build &&
apk del nodejs &&
apk del npm &&
mv /var/www/localhost/htdocs/build /var/www/localhost &&
cd /var/www/localhost/htdocs &&
rm -rf * &&
mv /var/www/localhost/build /var/www/localhost/htdocs;CMD [“/bin/sh”, “-c”, “exec nginx -g ‘daemon off;’;”]WORKDIR /var/www/localhost/htdocs
Let’s look at the size of the container before we build it again:
docker images | grep “reactdocker”;# [Expected Output]
# reactdocker latest 3c160b1a5941 16 minutes ago 489MB
Now we’ll build it again:
docker build . -t reactdocker;# and see the size difference
docker images | grep “reactdocker”;
# [Expected Output]
# reactdocker latest 669c991b23b6 20 seconds ago 36.6MB
That’s a huge difference from 489MB to 36.6MB.
Now run the container again:
docker run -it -d -p 80:80 –name rdocker reactdockerOptimized React Docker Container
Now that we have our optimized Docker image, let’s push it to Docker Hub.
docker tag reactdocker {docker-hub-username}/reactdocker;
docker push {docker-hub-username}/reactdocker;
After we’re successfully created our container, we’ll now go into Digital Ocean and deploy that same image.
Digital Ocean Create Droplet
Set some configuration for the Droplet:
Select pre-configured Docker One-Click appChoose a small size because this is just a demoChoose a region that is close to your for speedMake sure you’re ssh is set up correctly and create the droplet
Wait for things to complete and copy the IP address:
Copy Droplet IP address once complete
Now that we have the IP address, we can SSH into the container and perform the same docker run action:
# replace this droplet IP address with yours
ssh root@142.39.140.136;
It might prompt you for adding the key to the droplet, say yes.
Now that you’re in the droplet, let’s create that docker container:
docker run -it -d -p 80:80 –name rdocker {docker-hub-username}/reactdocker;
Once it’s up and running go the IP address in your browser, and you’ll see the react application is now running on the Digital Ocean droplet.
React running on Digital Ocean with Docker
We’ve now successfully packaged a ReactJS application in Docker and deployed it to Digital Ocean.
There are a few other things we could have done to improve things, including:
- Start tagging images with specific version to match a GitHub branch or tag
- Create a reverse proxy with a backend under the same url
- Create an SSL certificate to serve over HTTPS
- We could go a bit further into the nginx configuration with https://nginxconfig.io
I’ll create some more tutorials for those later on.
Any feedback or praise would greatly be appreciated.
Please share it on twitter or other social media platforms. Thanks again for reading.
Please also follow me on twitter: @codingwithmanny and instagram at @codingwithmanny.