React configuration file for post deployment settings

Not sure about the linux approach, but I am using create-react-app (cra), Docker & kubernetes a lot and initially struggled with similar problem. I was then inspired by https://github.com/inloop/cra-docker and was able to find a solution for the configuration file problem with create-react-app at runtime in both Docker and kubernetes. Below are the steps in my solution:

  1. Have your custom configuration (eg: config.js) ready. The contents inside your config file should look like this:

     window.ENV = {
         "ENVIRONMENT":"stg",
         ...other key-value configuration as you'll need
     }
    

    Your configurations will be accessible anywhere in your code by accessing window.ENV.your_configuration_key (eg: the ENVIRONMENT value above is available at window.ENV.ENVIRONMENT)

  2. Under public directory, edit the index.html and add

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

    in your head before the body. And put config.js under public directory.

    Your goal in solving the external configuration for cra is that you want to put your config.js file outside source directory and put it under the static public directory. If you put the config under source directory, the config will get compiled during build time so you won't able to change the config during runtime easily (well, templating works too but it's not ideal to me). Do note that serving files from static directory require a server, but since I'm already using nginx anyway to serve my static react app, I have absolutely no trouble doing this.

  3. Since I was already using nginx to serve the static files for my react app, I don't need to make changes to my Dockerfile to cater for additional config.js as it will be available under the build directory after compiled (due to it being placed under public directory). My Dockerfile looks something like this:

     # Step 1: Build static react app
     FROM node AS builder
    
     # Define working directory and copy source
     WORKDIR /app
    
     COPY . .
    
     # Install dependencies and build whatever you have to build 
     RUN yarn install && yarn build
    
     # Step 2: Run image
     FROM nginx
    
     COPY --from=builder /app/build /usr/share/nginx/html
     RUN rm /etc/nginx/conf.d/default.conf
    
     COPY nginx.conf /etc/nginx # this step is not required, only when you have custom nginx configuration
    

    Then afterwards build your docker image:
    docker build -t [your-docker-image]:latest .

  4. Last but definitely not least, you'll want to replace your config.js file during runtime. This can now be done easily.

    If you're running using docker, you can replace files by using the -v command. So your docker runtime command should look something similar to this:

     docker run --name [app-container-name] -d -p [host_port]:80 \
         -v [path_to_config.js_file_in_certain_environment]:/usr/share/nginx/html/config.js \
     [your-docker-image]
    

    If you're running using kubernetes, you can replace files in an existing directory under your container by using configMap, volume, volumeMounts and subPath.

    • First put your config.js under k8s ConfigMap:

      kubectl create configmap [k8s_config_name] --from-file=config.js=[path_to_config.js_file_in_certain_environment]

    • Mount your configMap in your k8s deployment:

      containers:        
        ...
        volumeMounts:
        - name: [k8s_config_name_volume]
          readOnly: true
          mountPath: "/usr/share/nginx/html/config.js"
          subPath: "config.js"
      
      volumes:
        - name: [k8s_config_name_volume]
          configMap:
            name: [k8s_config_name]
      

    Note that both mountPath and subPath parameters are necessary to replace a file under a directory that already has some files existing in it. If subPath is omitted during volume mount to an existing directory which already contains some files the result is unfavourable in our case cos it will override the existing directory by copying the new file into the directory but removing all other previously existing files.


I've managed to hack together a solution.

in the public folder 'config.js'

var config = {
      x: 'y',
};

Next wrap the ReactDOM.render (App/index.js in a fucntion like so

window.RenderApp = (config) => {
   ReactDOM.render(<App _config={config}/>, document.getElementById('root'));
}

In the index.html add these lines, the window.RenderApp HAS to be at the end, because it relies on bundle.js being imported which is auto added by react and has a random name in production.

</html>
...
<head>
...
<script type="text/javascript" src="%PUBLIC_URL%/config.js"></script>
...
</head>
...
<body>
...
</body>
<script>
   window.RenderApp(config);
</script>
</html>

lastly to use the config variables in your App.js or what ever you called it

...
constructor(props) {
    super(props)
    console.log(this.props._config)
    this.state = {
       ....
       config: this.props._config,
   }
}
...

I found you have to set config to a state variables or else it will randomly throw undefined for the object, now just pass config down the hierarchy to use in your code.