This tutorial will show you how to run your JavaScript front end in a Docker Container. Your application won’t be able to serve itself, being that it consists of HTML and JS files, and other such static content. We’ll need to serve our application through a web server.
No web servers will understand our application handles its own routing. Meaning, we’ll have to configure it in such a way that it passes our URIs to our landing HTML file, typically index.html.
NGINX Container
One of the easiest methods to get your Angular or React frontend up and running is to use the NGINX image. The image comes preconfigured and optimized for static file hosting.
docker pull nginx
Handling Routing
You fired up your application and discovered that none of your routes work. All URI’s are expected to be files or folders in the root web directory. Routes in Angular, React, or any other application aren’t known to NGINX.
In order for NGINX to handle our routes correctly we need to instruct it to pass all URI requests to a specific file — our index.html, for example. This offloads all routing to our application since all URI’s will be forwarded to index.html.
To configure NGNX we’ll need to grab a copy of the nginx.conf file. To do this we will need to run an NGINX container and then grab a copy of the conf file.
docker -d nginx
Using the first few characters of the container ID outputted when it started, run the following command to copy the nginx.conf file.
docker exec -it <docker id> cp /etc/nginx/conf.d/default.conf .
You now have a copy of the configuration. Open the configuration into a text editor.
vi default.conf
Find the following section in the configuration file.
location / { root /usr/share/nginx/html; index index.html index.htm; }
and then modify it to look like the following example.
location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html?$args; }
Our change instructs NGINX to first look for a file or folder that matches the URI requested. If neither exists, pass all URI’s to index.html.
Starting a Container
To quickly start your frontend on your local machine or on a server is to just run the container.
The container by itself will only host a static HTML file. We will have to add our frontend’s files to the container when it’s started using volumes.
docker run -d -p 80:80 -v /path/to/your/files:/use/share/nginx -v nginx.conf:/etc/nginx/conf.d/default.conf:ro -n myfrontend nginx
the above command will run the container continuously like a server service, expose port 80 of the container on port 80 of the host, and mount our static files to NGINX’s default web root directory. It also replaces default.conf with our customized version and places the file in Read-Only mode.
Building An Images
Running containers by adding your files and settings at startup is fine, but it will make it difficult to keep your environments consistent. Any server running your frontend should have the exact same configuration. When you start modifying your code or NGINX settings, consistency becomes more difficult.
Another reason why you should always build your own images Is version control. Every change to the image will have its own image that can be audited or rolled back to.
Deployments are simplified too.
- Create a directory to store you Docker image configuration and files.
mkdir ~/projects/myapp-docker
- Copy your custom nginx.conf file (completed above) to the project folder.
cp nginx.conf ~/projects/myapp-docker
- Change into your new project directory.
cd ~/projects/myapp-docker
- Create a new Dockerfile.
touch Dockerfile
- Open the Dockerfile into a text editor.
vi Dockerfile
We’re going to base our image off of the NGINX one. This simplifies a lot of the work that needs to be done. Add the following line to your Docker file.
FROM nginx:latest
Some applications require environment variables to be set to determine whether it is running in production, for example. You can set these by adding ENV to your Dockerfile. For example, we’re setting the environment to production and disabling debug output for our application. The forward slash allows us to create one ENV command with multiple entries.
ENV environment production \ debug false
Your static files need to find their way onto the image in order to be served. Add the following line to your Dockerfile, matching it the location of your application’s project files. It will add your files and subdirectories to the default NGINX document root of the NGINX image.
COPY /path/to/my/project /usr/share/nginx/html \ default.conf /etc/nginx/conf.d/default.conf
Our web application won’t be accessible unless we expose it over port 80. Add the following line to allow traffic over port 80 to reach the container.
EXPOSE 80
Putting it all together, your Dockerfile should resemble the following example.
FROM nginx:latest ENV environment production \ debug false COPY /path/to/my/project /usr/share/nginx/html / default.conf /etc/nginx/conf.d/default.conf EXPOSE 80
Without Dockerfile created we start building our image. Whenever you build your image you will need a name, at the very least. An optional but recommended step is to also add a unique tag to your image’s name, such as a version number.
In the following example, we are building an image named myapp and giving it the tag of 1.0.0, to match our application’s version.
docker build -n myapp:1.0.0 .
Every time you release a version of your application you should also create a matching image. It might be beneficial to add this to your production build scripts to eliminate manual steps.
Running Your React Or Angular Container
We have our image and now it’s time to launch it. Everything the image needs is already configured, so running our container is as simple as starting it.
docker run -d -p 80:80 myapp:1.0.0
After starting your container get into the habit of verifying it actually started and is running. You can use the ps docker command to check the status of your container.
docker ps
Production Docker Images
The default NGINX image is based on Debian 9. It’s a rather large image before your application is even copied to it. For those who want to be as lean as possible, there is a Linux Alpine version of the NGINX container. Let’s compare sizes.
REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest ae513a47849c 2 weeks ago 109MB nginx alpine ebe2c7c61055 5 weeks ago 18MB
Right away you see a very drastic size reduction from the standard NGINX image and the Alpine version. That latter is 83% smaller. Having a smaller image makes storage management a lot easier. You might be deploying several production builds a day, possibly every hour. Docker is a hoarder, in that it will not remove unneeded containers.
Every container ran is kept on disk, consuming precious storage and just cluttering your storage. We can lessen the storage consumption issues by switching to a smaller base image.
Having smaller images also means we can keep a longer history of builds. This could be beneficial if you need to do an emergency rollback to an image released two weeks ago in an environment that performs hourly builds.
Let’s update our Dockerfile to use the NGINX Alpine image.
FROM nginx:alphine ENV environment production \ debug false COPY /path/to/my/project /usr/share/nginx/html / default.conf /etc/nginx/conf.d/default.conf EXPOSE 80
And finally, let’s build a new image for our Alpine version.
docker build -n myapp:1.0.0 .
If you already have an image with the name myapp:1.0.0, the new build will overwrite it. No need to delete our old Debian-based NGINX images.