Dockerizing a React app

So you have a React app. And you want to serve it through Docker.

Let's do that!

At the end of this tutorial, you'll have a Docker container running your app that you can deploy as you see fit 👌

We're going to start from an existing app - a barebones app, using just Webpack and React. Find the starter code on Github or follow a step by step tutorial to setup the app.

Step 1: Building a Docker image

To build a Docker image, we want to copy all the source files inside the container, build the project (also inside the container) and then serve the build folder.

Let's start by ignoring the files that we never want to copy to the docker image. For this, we'll create a .dockerignore file in the root of the project:

// .dockerignore
node_modules
build

Next, let's define our Docker container by creating a Dockerfile in the root of the project:

// Dockerfile

# ==== CONFIGURE =====
# Use a Node 16 base image
FROM node:16-alpine 
# Set the working directory to /app inside the container
WORKDIR /app
# Copy app files
COPY . .
# ==== BUILD =====
# Install dependencies (npm ci makes sure the exact versions in the lockfile gets installed)
RUN npm ci 
# Build the app
RUN npm run build
# ==== RUN =======
# Set the env to "production"
ENV NODE_ENV production
# Expose the port on which the app will be running (3000 is the default that `serve` uses)
EXPOSE 3000
# Start the app
CMD [ "npx", "serve", "build" ]

Notes

  • Using `alpine` flavour of an image (e.g. `node:16-alpine` instead of `node:16`) will give you a smaller image size
  • Check what the latest LTS Node version is and use that Docker image. At the time of writing of this article, it was Node 16
  • For simplicity, we're just serving the app with npx serve for now. Step 2 will add nginx, so stay tuned!

Ok, let's build the Docker image and run it, to make sure everything works.

# Build the Docker image for the current folder 
# and tag it with `dockerized-react`
docker build . -t dockerized-react

# Check the image was created
docker images | grep dockerized-react

# Run the image in detached mode 
# and map port 3000 inside the container with 3000 on current host
docker run -p 3000:3000 -d dockerized-react

Now open the app at http://localhost:3000 - you should see "Hello from React" 😀

Step 2 - Serve the app through nginx

While serving the app with serve is ok for small apps, it's common to just use nginx to serve the static files, so let's set that up as well.

Also, while we're at it, we'll separate the "build" and "run" steps of creating the Docker image by taking advantage of Docker image layers. This allows us to run the build in a node image and server the app using an nginx image.

To begin, let's create an nginx.conf file in the root of the project:

// nginx.conf

server {
  listen 80;

  location / {
    root /usr/share/nginx/html/;
    include /etc/nginx/mime.types;
    try_files $uri $uri/ /index.html;
  }
}

Next, let's update the Dockerfile to with separate layers and serving the app with nginx:

FROM node:16-alpine as builder
# Set the working directory to /app inside the container
WORKDIR /app
# Copy app files
COPY . .
# Install dependencies (npm ci makes sure the exact versions in the lockfile gets installed)
RUN npm ci 
# Build the app
RUN npm run build

# Bundle static assets with nginx
FROM nginx:1.21.0-alpine as production
ENV NODE_ENV production
# Copy built assets from `builder` image
COPY --from=builder /app/build /usr/share/nginx/html
# Add your nginx.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

Now, the final Docker image will just contain the build folder and nothing else - the project files were only used by to build the project in the builder layer, which then gets thrown away - it's just an intermmediary step.

Cool, now let's check that this works by building and running the Docker image.

First, let's stop the previously started container:

# See all running containers
docker ps

# Stop the previous container
# Copy "Name" or "Container ID" of your container
# and pass it into docker stop
docker stop <container name|id>

Next, let's rebuild the image and run it:

docker build . -t dockerized-react

# Notice we're now mapping port 80 inside the container 
# to port 3000 on the host machine!
docker run -p 3000:80 -d dockerized-react

Navigate to http://localhost:3000 to see it's still working!


That's it! Let me know in the comments below if this worked for you 💪

You can checkout the repository for this tutorial at: https://github.com/jsramblings/dockerize-react