Image for post
Image for post

Using Kubernetes ConfigMaps and Docker Env Variables in FE projects

Ahmad Mhaish
Dec 20, 2019 · 6 min read

After moving to Microservices architect, and by using a framework like Kubernetes, the need to store all configuration and variables in an easy to access place appears. And that is the main benefit of Kubernetes ConfigMaps and Secrets.

These two objects let you store and manage in order your configurations and sensitive data. Like integration endpoints, hidden/shown features, passwords, tokens, ssh keys, …etc.

The real magic comes when you can use these values in your Kubernetes service deployment file where you are defining the Docker instance and environment parameters, which means that these parameters can be passed as environment parameters to any service in the mesh.

One of the challenges is to find a way to pass these parameters to a front-end project either if it's a pure JS project or written in one of the famous platforms now (React, Angular and Vue). We will talk next about configuring Kubernetes ConfigMaps, using them in services, and the trick to using these variables in FE project.

Defining ConfigMap/Secret in K8

K8s secrets are all about that Base64 encoding. Kubernetes does this to avoid accidentally exposing secrets.

As you can find in K8 official website about the secrets:

To use a secret, a pod needs to reference the secret. A secret can be used with a pod in two ways: as files in a volume mounted on one or more of its containers, or used by kubelet when pulling images for the pod.

Secrets can be created using kubectl command directly like the example in the previous website, or the best way always by using yaml file, so we store them in a repository as the first step, and maybe setup a CI/CD pipeline for them.

Next is an example using yaml file to create the secrets:

apiVersion: v1
kind: Secret
metadata:
name: my-secrets # The name of the secret which can contain a group of variables
type: Opaque
data:
integrations.url: aHR0cDovL2ludGVncmF0aW9uLnVybA== # The name fo the variable: the value of that variable in base64 format

Thankfully though, encoding in base64 is pretty easy. Just run the following command with your secret value to encode it:

$ echo -n some_text_to_encode | base64
c29tZV90ZXh0X3RvX2VuY29kZQ==

— Don’t drop the -n flag, you may run into issues if you drop it because k8s secrets don’t play nice with newline characters.

Similarly, you can decode a base64 encoded text as well:

$ echo c29tZV90ZXh0X3RvX2VuY29kZQ== | base64 -d
some_text_to_encode

ConfigMaps on the other hand doesn’t need to be base64 encoded, just inside them we are storing normal string data, next is an example of that:

apiVersion: v1
kind: ConfigMap
metadata:
name: frontEndConfig
data:
app.google.tag.id: GGGGGGGT

In this example, I am storing the Google tags manager account id where there is no problem to expose that to Frontend.

Please note: While you can use ConfigMaps and Secrets as you want, K8 team designed ConfigMaps for configurations and Secrets for secure data, so logically if you are storing sensitive data inside Secrets you should not use them inside your FE project or they will be exposed to anyone. Even don’t share secrets with a Backend service that does not use them, if that service is hacked then all of the secrets will be exposed. It is always a good practice to create multiple ConfigMaps and Secrets and use them in the services according to the needs.

Adding our ConfigMaps/Secrets to our app’s Container

Now that we have our ConfigMap / Secrets, we need to make a few modifications to our Container’s deployment.yaml in order to use them.

Next is an example of that deployment file for a back-end service:

apiVersion: apps/v1
kind: Deployment
metadata:
name: back-end-project
labels:
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: back-end-project
template:
metadata:
labels:
app: back-end-project
version: v1
spec:
containers:
- name: back-end-project
image: <container image path>
ports:
- containerPort: 80
env:
- name: MONGODB_URL # The name of env variable
valueFrom:
secretKeyRef:
name: my-secrets # The name of the secret
key: mongo.url # The name of the variable stored in the secret
- name: TargetServicePath
valueFrom:
configMapKeyRef:
name: my-config # The name of the ConfigMap
key: app.emails.endpoint # The name of the variable stored in the ConfigMap

The trick to use ConfigMap/Secret in FE project

The main problem in front-end projects is that they are working in the browser, so it will be too hard and not logical to access Docker env variables on the server from the FE project in the browser. Usually, these projects Docker containers compose of Nginx container that is serving the project to be loaded to the browser.

The trick to turning around this problem is to find a way to load these variables inside the FE project after starting the container, so the project can use them in run-time in the browser through importing a JS file. A good option is to use an external script that is generating that JS file and we set these variables to the window object inside that file, so when the browser loads that script it will automatically load the variables to the window object and be exposed to the whole FE application.

The first step to do that is to achieve that in the local then implement that in Docker file. So let’s create a .env file in our FE project, putting all variables there, and then create a script that will generate our variables JS file. Next bash script can help you achieve that:

#!/bin/bash

# Generating our script config file
rm -rf ./env-config.js
touch ./env-config.js
# Adding the assignments inside that file
echo "window._env_ = {" >> ./env-config.js
# Read each line in .env file, each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
do
# Split env variables by character `=`
if printf '%s\n' "$line" | grep -q -e '='; then
varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
fi
# Read value of current variable if exists as Environment variable
value=$(printf '%s\n' "${!varname}")
# Otherwise use value from .env file
[[ -z $value ]] && value=${varvalue}
# Append configuration property to JS file
echo " $varname: \"$value\"," >> ./env-config.js
done < .env
echo "}" >> ./env-config.js

And the source of these variables will be either Docker env variables if they exist or the .env file parameters.

We need to import the env-config file in the index.html file in a suitable way for each framework.

We can use our variables afterward directly inside the JS code or HTML code, next is an example of that:

<div>My variable: {window._env_.my_variable}</div>

And then in development mode, we will need to add a call to env.sh file we created before starting the project, like:

For Angular:

"scripts": {
"dev": "chmod +x ./env.sh && ./env.sh && cp env-config.js ./public/ && ng serve",
},

For React:

"scripts": {
"dev": "chmod +x ./env.sh && ./env.sh && cp env-config.js ./public/ && react-scripts start",
},

And when talking about production, just we need to execute that env.sh file from the Docker file, like:

FROM nginx:1.15.2-alpine
# Nginx config
RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx
# Static build
COPY --from=builder /app/build /usr/share/nginx/html/
# Default port exposure
EXPOSE 80
# Copy .env file and shell script to container
WORKDIR /usr/share/nginx/html
COPY ./env.sh .
COPY .env .
# Add bash
RUN apk add --no-cache bash
# Make our shell script executable
RUN chmod +x env.sh
# Start Nginx server in addition to calling env.sh to generate our script file
CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""]

Then after running the Docker image, our container will have the generated env.config file ready to be download from the browser side whenever a user requests the FE project. Next is an example of that deployment file with details:

apiVersion: apps/v1
kind: Deployment
metadata:
name: front-end-project
labels:
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: front-end-project
template:
metadata:
labels:
app: front-end-project
version: v1
spec:
containers:
- name: front-end-project
image: <container image path>
ports:
- containerPort: 80
env:
- name: INTEGRATION_POINT_URL # The name of env variable
valueFrom:
secretKeyRef:
name: my-secrets # The name of the secret
key: integrations.url # The name of the variable stored in the secret

By that time we got one place to manage system parameters in a secure and centralized way, including FE projects.

The Startup

Medium's largest active publication, followed by +756K people. Follow to join our community.

Ahmad Mhaish

Written by

I am a Software Architect and AI engineer that have a great passion for integrating technology with businesses and human life.

The Startup

Medium's largest active publication, followed by +756K people. Follow to join our community.

Ahmad Mhaish

Written by

I am a Software Architect and AI engineer that have a great passion for integrating technology with businesses and human life.

The Startup

Medium's largest active publication, followed by +756K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store