How to use react environment variables: The complete guide


In this post I'd like to share my approach on building React containers that are configurable at deployment time.

While building FloodRunner, I ran into an interesting problem where I wanted to publish a react container for the front-end of FloodRunner but I needed customers to be able to easily specify their own parameters when deploying the container.

There is quite a lot of information on using environment variables with React but most of these deal with build-time configuration, so the container would be built with a specific configuration and every time you wanted to apply a new configuration you would have to rebuild the container. This is less than desirable and so I needed to find a different approach.

The approach I have taken is to serve a config.js file along with the React app that contains the environment/customer specific variables. This file can then be replaced during deployment making the container environment aware. I'll walk you through how to setup this config file and then how to deploy it using docker-compose and Kubernetes.

TL;DR Here is the GitHub Repository showing a sample React project that is environment aware

javaadpatel/react-docker
Building React containers that are configurable at deployment time - javaadpatel/react-docker

Creating the React app with Configuration

We'll start with a blank React app and will be using TypeScript. First create the project using

npx create-react-app my-app --template typescript

once created, start the app in development mode using

npm run start
Template React application

Then create a file called config.js in the public folder of your project and paste in the following code

window.API_URL = "api.development.com"

This will represent our environment variable that we want to change at deployment time. You can use any variable name here as long as you have it as a property of the window object.

Then in your index.html add the following line to the <head> element

<script src="%PUBLIC_URL%/config.js"></script>

This will ensure that the configuration file is loaded along with the application.

Now we're ready to consume the environment variable, so we can create a helper class to make this cleaner. Create a file called config.ts in your src folder and paste in the following code

const envSettings = window as any;
export class Config {
  static api_url = envSettings.API_URL;
}

Then in your App.tsx file you can use the variable

API URL: {Config.api_url}

This now forms the basis of our configurable React application and we can move on to creating the docker container.

Creating the React container

We'll be creating the Docker container and using Nginx as the web server. Create a file called Dockerfile in your root directory and paste in the following

FROM node:alpine as builder
WORKDIR '/app'
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html

In this Docker file we are taking advantage of Docker's multi-stage build in order to build the React app and then copying its contents over into our Nginx image. We also specify a default.conf file to setup our Nginx server and so need to create this file in our project. Create a directory named nginx and then a file default.conf in this directory and then paste in the following

server {
  listen 3000;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
  }
}

This tells our Nginx container where the application files will be placed and also tells it to listen on port 3000 which is the same port we exposed in our Dockerfile.

Now we can build the container using

docker build -t react-container .

This will build our container and tag it with the name react-container. We can then run the container using

docker run -p 3000:3000 react-container

This will start our react app on port 3000 and we can check that its working by going to http://localhost:3000.

Deploying using Docker-Compose and Kubernetes

Now we have a useable react container and our next step is seeing how we can deploy this using docker-compose or Kubernetes. I've decided to walk through both approaches as sometimes you won't be using a full-scale Kubernetes environment but instead just running docker-compose.

Docker-Compose deployment

Create a docker-compose.yml file in your root directory and paste the following

version: "3"
services:
  web:
    build:
      context: .
    ports:
      - "3000:3000"
    volumes:
      - ./config-production.js:/usr/share/nginx/html/config.js

NB. Ensure that this file is located at the same level as your Dockerfile, otherwise you will have to edit the build options of the docker-compose file.

This docker compose file maps the port 3000 on your localhost to the container port 3000 and it also replaces the config file ensuring that we use the new values specified. You need to create a config-production.js file and paste the following

window.API_URL = "api.production.com";

Then we can run docker-compose up and navigate to localhost:3000 and see our new configuration in effect.

Kubernetes deployment

We'll be deploying to our local Kubernetes cluster but the same would apply for a production cluster (aside from the NodePort service). There are three main aspects of the Kubernetes deployment:

1) Service - This allows Kubernetes to locate the React application and route traffic to it

2) Config Map - This will hold our environment specific configuration

3) Deployment - This specifies how to deploy the React application and in what configuration (image, pods, etc) to run it in

1) Creating the service

To get started, create a folder named k8s in your root directory. Then create a file named web-service.yml and paste the following

apiVersion: v1
kind: Service
metadata:
    name: web-service
spec:
    type: NodePort
    selector:
        component: web
    ports:
        - port: 80 #port to receive traffic on
          targetPort: 3000 #port that container is listening on
          nodePort: 30000 #exposes this via localhost port 30000 (DO NOT USE IN PRODUCTION)

This will create a Kubernetes service that routes traffic to your react container making it accessible via localhost:30000. In this example we use a NodePort service for local development, but in a production environment we would use a ClusterIP instead and then expose it via a reverse-proxy such as Traefik. You can learn how to deploy Traefik in your production environment here.

2) Creating the ConfigMap

ConfigMap's allow us to easily group our configuration settings in a single file. Create a file named web-configmap.ymland then paste the following

apiVersion: v1
kind: ConfigMap
metadata:
    name: web-config
data:
    web.properties: |
        window.API_URL= "api.kubernetes.com"

In this file we specify our environment variable that we've been using.

3) Creating the deployment

Next we create the Kubernetes deployment. Create a file named web-deployment.yml and paste the following

apiVersion: apps/v1
kind: Deployment
metadata:
    name: web-deployment
spec:
    replicas: 1
    selector:
        matchLabels:
            component: web
    template:
        metadata:
            labels:
                component: web
        spec:
            containers:
                - name: web
                  image: react-container
                  imagePullPolicy: Never #used for getting it to pull local images
                  ports:
                      - containerPort: 3000
                  volumeMounts:
                      #override config.js file
                      - name: config
                        mountPath: "/usr/share/nginx/html/config.js"
                        subPath: "config.js"
            terminationGracePeriodSeconds: 10
            volumes:
                - name: config
                  configMap:
                      name: web-config
                      items:
                          - key: "web.properties"
                            path: "config.js"

This deployment file specifies the image to pull (react-container) and then also mounts the ConfigMap to replace the config.js file in our container.

We can then deploy the Kubernetes configuration files using kubectl apply ./k8s this will apply all our configuration files in the k8s directory. When we navigate to localhost:30000 we should then see this

And with that our Kubernetes deployment is complete.

Where to from here?

As with everything in React, this is not the only approach but I hope that this post has been of help in understanding one way to tackle this problem and gives you a complete guide on how to go from local development to production.

Now that you have a fully configurable React container, you'll be able to deploy multiple instances of it each only differing in the configuration passed through or distribute container images to your customers 💻.

Leave me a comment if this has helped you or if you have ideas on how this approach can be enhanced.

Javaad Patel

FullStack Developer

I'm passionate about building great SaaS platform experiences. Currently learning and writing about cloud architectures, distributed systems and devOps.