ReactJs Environment variables in Kubernetes

Younes Kejji
OCP digital factory
3 min readFeb 9, 2021

Let’s say you are working on a reactjs web application deployed on Kubernetes. The app needs some configuration (e.g. url of analytics server, clientId, redirectUrl … for oAuth …).

Somewhere in the application you are supposed to define config variables. One way is creating a config file « appConfig.js » and its content could look like :

var appConfig = {
OAUTH_CLIENT_ID: ‘myappoauthclientid’,
OAUTH_REDIRECT_URI: ‘https://myapp.com/‘,
ANALYTICS_URL: ‘https://live.analytics.com’,

};

That’s fine, but what if the application is deployed on three different environments (staging, preproduction and production) and the configuration is not the same on each env?

To deal with this situation, I’ll be presenting three approaches. The first one is at build time, second one at runtime but in a static way and the last one (my favorite) is also at runtime but dynamic way.

1- Build-Time :

The first approach is really simple, the goal is to generate a different app for each environment. This can be achieved by :
- Maintaining 3 branches and each with appropriate content of the file « appConfig .js » : the problem with this option is that Merge/Pull requests between branches is not smooth (we should avoid overriding configs from other environments)
- Inject process.env variables during build process and define values in the ci/cd descriptor.

2- Runtime / static:

In this approach, the config file will contain values for all environments, then according to a variable (most common is hostname) we decide which variable to use:

config:

var appConfig = {
OAUTH_CLIENT_ID_PROD: ‘myPRODappoauthclientid’,
OAUTH_REDIRECT_URI_PROD: ‘https://myapp.com/‘,
ANALYTICS_URL_PROD: ‘https://live.analytics.com’,
OAUTH_CLIENT_ID_PREP: ‘myPREPappoauthclientid’,
OAUTH_REDIRECT_URI_PREP: ‘https://prep.myapp.com/‘,
ANALYTICS_URL_PREP: ‘https://prep.analytics.com’,
OAUTH_CLIENT_ID_STAGING: ‘mySTAGINGappoauthclientid’,
OAUTH_REDIRECT_URI_STAGING: ‘https://staging.myapp.com/‘,
ANALYTICS_URL_SATGING: ‘https://staging.analytics.com’,

};

Application code:

const hostname = window && window.location && window.location.hostname;
let ANALYTICS_URL = “https://defaultanalytics";
//check undefined ….
if (hostname.indexOf(“myapp-staging.com”) >= 0) {
ANALYTICS_URL = appConfig.ANALYTICS_URL_STAGING;
}else if (hostname.indexOf(“myapp-preprod.com”) >= 0) {
ANALYTICS_URL = appConfig.ANALYTICS_URL_PREPROD;
}else if (hostname.indexOf(“myapp.com”) >= 0) {
ANALYTICS_URL = appConfig.ANALYTICS_URL_PROD;
}
}

Cons:
- Changing hostnames and variable values leads to a new version of the app.
- packing unnecessary information in the app

3- Runtime / Dynamic (using k8s configmaps):

The idea here is to use the same app container and according to the environment we inject the right variables using configmaps.
The application will load the js file « appConfig.js » but the content is provisioned (mount) through volume configmap.

The ConfigMap for staging env looks like this:

apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-cm-file
namespace: myapp
data:
appConfig.js: |
var appConfig = {
OAUTH_CLIENT_ID: ‘mySTAGINGappoauthclientid’,
OAUTH_REDIRECT_URI: ‘https://staging.myapp.com/‘,
ANALYTICS_URL: ‘https://staging.analytics.com
};

In the k8s deployment descriptor we mount the previous configMap in the folder config of nginx html location.

NB: Location should be set according to docker base image (nginx, apache, …)

 containers:
— name: front-myapp
image: docker/front-myapp:latest
volumeMounts:
— mountPath: /usr/share/nginx/html/config
name: config-files
ports:
— name: http
containerPort: 80
volumes:
— configMap:
defaultMode: 420
name: myapp-cm-file
optional: true
name: config-files

Now let’s import the config script in the index.html file:

<link rel=”icon” href=”/favicon.ico” />
<title>My App</title>
<script src=”/config/adfs-configs.js”></script>
</head>
<body>

To access/use the variables, all we need to do is getting them from window object:


let ANALYTICS_URL = window?.appConfig?.ANALYTICS_URL;

One more step, useful for local environment is creating a default appConfig.js file in the <source_main_dir>/config/ folder. And make sure the the file is exposed (not packed).

Local config file:

const appConfig = {
“OAUTH_CLIENT_ID”: “mySTAGINGappoauthclientid”,
“OAUTH_REDIRECT_URI”: “https://staging.myapp.com/",
“ANALYTICS_URL”: “https://staging.analytics.com"
};
/* istanbul ignore next */
module.exports = appConfig;

In case of webpack:

webpack.base.babel.jsmodule.exports = options => ({​
externals: {
appConfig: JSON.stringify(require( path.resolve(‘app/config/appConfig.js’))), //eslint-disable-line
},
});

Caution : Do never inject secrets in js/html

--

--