React Production Builds using Docker Compose

If you have ever needed to create a docker container that runs a React app but you need to use a docker-compose file to add environmental variables to it but need it to be flexible enough to use any compose file on any given server (stage, prod, test) then this post might be for you.

The Scenario

You have a React app, and you are trying to create a single docker container to deploy that once started will consume a given docker-compose files environmental variables, but you don’t want to store a build in the container with predefined environment variables.

Prerequisites

To understand this you have to have a basic knowledge of React and Docker. This article assumes that you have a React project and know how to create a React project. The purpose is more of an in-depth understanding of how to use a Docker Compose file to inject environmental variables into your production build at the start of your container. If you don’t understand the basics of React please check out this first.

What you will need

0. React project and a base understanding of what a build is for it.
1. Dockerfile
2. package.json
3. docker-compose.yml
4. strong stomach for debugging

Dockerfile
First start by creating your Dockerfile. Here we create a working directory, and for security best practice we add a non-root user. Then we add the node_modules to the path, install all our node_modules, the react-script install and finally the serve. Lastly we call start to kick off the process when the container starts up. The CMD at the end calls the package.json start script to kick off your build and start the server.

FROM node:10.15.0

# set working directory
RUN mkdir /usr/src/reactapp
WORKDIR /usr/src/reactapp

# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/reactapp/node_modules/.bin:$PATH

# TODO: for security purposes, you should update this Dockerfile to specify your own target user/group
# -R changes the ownership rights of a file recursively
RUN addgroup --system company-group && adduser --system company-user --ingroup company-group && \
	chown -R company-usercompany-group /usr/src/reactapp

# Denotes to copy all files in the service to '/usr/src/reactapp' folder in the container
# install and cache app dependencies
COPY --chown=company-user:company-group . /usr/src/reactapp

RUN npm install --silent 

USER root
RUN npm install react-scripts@3.0.1 -g --silent 
RUN npm install -g serve --save

# start app
CMD ["npm", "start"]

NOTE: Silencing the NPM output, via –silent, is a personal choice, it can swallow errors. If you find you have errors change –silent to –verbose

package.json
Next we see a portion of the package.json file. This is the only part you need to focus on, as I assume your packages would be specific for the project you are working on.

The most important part to see on this is the start script. This does the build of your project, then it starts the build with the serve command using port 8080 (the port is what ever you want it to be. In my case i was using 8080)

The debugging script shown here pulls all my environmental variables from a .env file so that i can dynamically run my project with out at docker images (but thats out of scope for this)

. . .
  "scripts": {
    "start": "GENERATE_SOURCEMAP=false react-scripts build --production && serve -s build -l 8080",
    "debugging": "env-cmd .env.development react-scripts start",
    "build": "GENERATE_SOURCEMAP=false react-scripts build --production",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
. . .

Once this is set up you can do your build. docker build -t my.docker.image:latest

docker-compose.yml
This is a basic version of a docker compose file. Here we are are using the image we created with our docker build, once it starts it uses our environment variables as seen here.

You will notice that some of these start with REACT_ this is important as the React project will not pick up any custom variables that don’t start with REACT_.

In this example all I want to pass in is a logo and company name for my site to display.

version: "3.2"
services:
  web:
    image: my.docker.image:latest
    ports:
    - 8075:8080
    expose:
    - "8080"
    environment:
        NODE_ENV: production
        REACT_APP_LOGO: cust_images/acme.png
        REACT_APP_COMPANYNM: Acme
        PORT: 8080 

Code Example

Here we see a very basic use case on how to get your environment variables to use. Plucking the variables out is pretty easy {process.env.REACT_APP_LOGO}.

import React, { Component } from 'react';
import { BrowserRouter } from 'react-router-dom';

class Thanks extends Component {

    render() {
        return ( 
            <BrowserRouter> 
            <>
            <header>
                <img className="venderLogo-thanks" src={process.env.REACT_APP_LOGO} alt="Vender Logo"/>
            </header>
            <div id="formWrapper" className="thanks-wrapper">
                <h1>Submission Success</h1>
                <p>Thank you for submitting your connection information.</p>
                {process.env.REACT_APP_COMPANYNM}
            </div>
            </>
            </BrowserRouter> 
            );
    }
}
  
export default Thanks;
Loading Facebook Comments ...