REACT PROJECT 

DYNAMIC ENV VARIABLES

Building docker image for React project

Disclaimer: The content of this blog are my views and understanding of the topic. I do not intend to demean anything or anyone. I am only trying to share my views on the topic so that you will get a different thought process and angle to look at this topic.

     A react app created using create-react-app framework follows a convention to resolve the environment variables during the build phase. The create-react-app achieves that using dot-env node package. Some convention in naming the environment config files is followed. A typical environment setup has three kinds development, staging and production. If you build an image and test it in development environment then the same image should be used for staging. Once tested in staging the same should be deployed in production. What changes with environment is environment specific settings or configurations. We should not rebuild an application again. Also if there is any BUG spotted in production environment and we want to replicate the same on the developer machine then we should take the production image and run on local with local configuration.

Assumptions

Folder structure

Consider if you created an app named "sample-react-project" then your folder structure would look something like this :

sample-react-project/

├── docker-entrypoint.d/

│   └── 99-generate-env-js.sh

├── Dockerfile

├── docker-compose.yml

├── .env

├── package.json

└── ...

Add reference of env.js in index.html

<!DOCTYPE html>

<html lang="en">

<head>

<script src="env.js"></script>

</head>

<body>

The body content is added here....

</body>

</html>

In the create react app project inside the public folder we have index.html file. Add a script tag for loading the env.js file as shown in the code snippet in the left panel. 

Contents of env.js

const app_env = {

APP_VERSION: 1.2.3

};

You can name a global constant like app_env or any desired name.

Note: This is how this file will be generated by using the values passed by docker-compose.yml environment variables. You don't need to create this file manually. The script 99-generate-env-js.sh will do it for you.

Create a script to generate env.js

#!/bin/bash

ENVJS=/usr/share/nginx/html/env.js

echo "const app_env = { " > $ENVJS

ENV_KEYS=( \

 "APP_VERSION" \

)

for key in "${ENV_KEYS[@]}"

do

 varname=$key

 value=$(printf '%s\n' "${!varname}")

 echo "  $varname: \"$value\"," >> $ENVJS

done

echo "};" >> $ENVJS

The script will generate the env.js file by reading the list of ENV_KEYS from the environment variables passed on by Docker container. We create this file at the location /usr/share/nginx/html inside the docker container. The script name should be 99-generate-env-js.sh. The integer prefix represents the order of the execution of the entrypoint scripts.

Place this script inside the folder docker-entrypoint.d of your react project

Docker environment variables can be accessed directly in a bash script which is executed as entrypoint script of the docker container.

Create a Dockerfile for the project

FROM node:18-alpine as builder

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

RUN npm run build


FROM nginx:alpine as server

COPY --from=builder /app/build /usr/share/nginx/html


RUN apk add bash

COPY docker-entrypoint.d/* docker-entrypoint.d/

RUN chmod +x docker-entrypoint.d/99-generate-env-js.sh

In the left panel you can see the docker file code for dockerizing this react app. It is a two stage docker build. In the first stage we build the project by copying all the source code inside the docker. It generates a build folder as per the output of the npm run build command from react scripts. In the second stage we copy the build folder from the first stage and run that on nginx image as server. 

Note that we are copying the docker-entrypoint.d folder from stage one to stage two of the docker build and assigning the executable permission to the script inside the folder.

Sample docker-compose.yml which will build and run this image

version: "3"


services:

 sample-react-project:

   container_name: sample-react-project

   image: sample-react-project:local

   ports:

     - 3000:80

   build: .

   env_file:

     - env-local

In the left panel you can see the docker-compose.yml file which builds the image and runs the same inside the docker. We are using an env file called env-local. Your machine port would be 3000 for running this app and URL would be http://localhost:3000.

Accessing the environment variable in a React Component

import React from "react";

import logo from "./logo.svg";

import "./App.css";


declare const app_env: any;


function App() {

 return (

   <div className="App">

     <header className="App-header">

       <img src={logo} className="App-logo" alt="logo" />

       <p>

         Edit <code>src/App.tsx</code> and save to reload.

       </p>

       <a

         className="App-link"

         href="https://reactjs.org"

         target="_blank"

         rel="noopener noreferrer"

       >

         Learn React {app_env.APP_VERSION}

       </a>

     </header>

   </div>

 );

}


export default App;

You can access this environment variable in a react component as shown in the left panel code. If you are using typescript template for generating the app then you will need to declare a placeholder variable in this component like declare const app_env: any; as shown in the code.

Summary

In this setup you can now build a docker image for the project and run the same image on different environments by changing the .env file or the environment variables passed to the service declaration inside the docker compose file.