DOCKERISED REACT APP CONFIG CHANGE WITHOUT RELOAD
Applicable only for non-sensitive configurations in a React App
Docker environment variables which are passed from docker-compose.yml as .env file or as environment tag require a container reload if you change their value. However if there are certain app configuration which should not require a container reload then a simplest appraoch is to chose volume mounting. We will see this approach in detail in this blog.
Assumptions
The app in run inside docker on a nginx docker image
The app skeleton code is created using create-react-app tool and no separate webpack configuration is done
We are configuring only non-sensitive values which does not require a restart
Folder structure
Create an app named sample-app using create-react-app tool. Below is a sample folder structure for the same :
sample-app/
├── public/
│ └── index.html
├── config/
│ └── env.js
├── docker-compose.yml
├── Dockerfile
└── package.json
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
In the adjacent panel you can see a Dockerfile 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.
Contents of env.js
const app_env = {
APP_VERSION: 1.2.3
};
In the app folder create a config folder and create a file named env.js inside that. Let us say it contains a configuration for APP_VERSION as 1.2.3.
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 an index.html file. Add a script tag for loading the env.js file as shown in the code snippet in the left panel.
Build and run using docker-compose.yml
version: '3'
services:
sample-app:
container_name: sample-app
image: sample-app:local
build: .
ports:
- 3001:80
volumes:
- ./config/env.js:/usr/share/nginx/html/env.js
In the left panel you can see the docker-compose.yml file which builds the image and runs the same inside the docker. The port binding shows that the app is running on your machine port number 3001 with URL http://localhost:3001. Volume mounting shows that ./config/env.js file is mounted inside the docker container at location /usr/share/nginx/html/env.js.
Note: Here it is important to mention the source volume mounting as relative location starting with a dot (Ex: ./config/env.js)
Accessing the environment variable in a React Component
import React from "react";
import logo from "./logo.svg";
import "./App.css";
interface AppEnv {
[key: string]: any;
}
declare const app_env: AppEnv;
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
Now you can build and run a docker image for sample-app and see if the value is reflected on the UI. Try changing the value of APP_VERSION in the config folder on your system and refresh the page. The value should reflected on the UI unless the Javascript is cached. In that case try clearing the cache and refresh the page. Cautiously decide to place only those configurations in such way which are not sensitive but should not require container reload. A good example would be a feature flag.