I have a react application that is hosted in a nginx container using static files that are prepared in a build step. The problem I run in to is that the API URL is then hard coded in the js files and I get a problem when I want to deploy the application to different environments.
So basically I have put a config.js file with the localhost API URL variable in the public directory which is then loaded in the application in the section of the index.html file. This works for the local environment. The problem comes when I want to deploy it to the test or production environment.
I have found out that it is possible to use a configMap with volume mounts, but that requires me to prepare one file for each environment in advance as I understand it. I want to be able to use the variables I have set in my Azure DevOps library to populate the API URL value.
So my question is if there is a way to replace the values in the config.js file in the nginx container using Kuberentes/Helm or if I can make use of a Azure DevOps pipeline task to replace the content of a pre-prepared config.js file and mount that using Kubernetes?
Not sure if it is clear what I want to do, but hopefully you can understand it...
config.js
window.env = {
API_URL: 'http://localhost:8080'
};
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My application</title>
<!--
config.js provides all environment specific configuration used in the client
-->
<script src="%PUBLIC_URL%/config.js"></script>
</head>
...
What I ended up doing was setting it up like this:
First I added a configmap.yaml to generate the config.js file
apiVersion: v1
kind: ConfigMap
metadata:
name: config-frontend
data:
config.js: |-
window.env = {
API_URL: "{{ .Values.service.apiUrl }}"
}
Values.service.apiUrl
comes from the arguments provided in the "Package and deploy Helm charts" task --set service.apiUrl=$(backend.apiUrl)
Then I added a volume mount in the deployment.yaml to replace the config.js file in the nginx container
...
containers:
...
volumeMounts:
- name: config-frontend-volume
readOnly: true
mountPath: "/usr/share/nginx/html/config.js"
subPath: "config.js"
volumes:
- name: config-frontend-volume
configMap:
name: config-frontend
This did the trick and now I can control the variable from the Azure DevOps pipeline based on the environment I'm deploying to.
You can achieve this in several ways. Following are the few.
1.ConfigMap
Most effective and best way to achieve this, like one of the added comments. You can do something like this with a single config map.
Example ConfigMap might look something like this
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.definitionName }}-{{ .Values.envName }}-configmap
namespace: {{ .Values.Namespace }}
data:
API_URL: '{{ pluck .Values.envName .Values.API_URL | first }}'
Example Values file in helm charts would look like this
API_URL:
dev: dev.mycompany.io
staging: staging.mycompany.io
test: test.mycompany.io
prod: mycompany.io
And before helm install or helm upgrade run add a step in Azure devOps to run the bash command on your CI/CD pipeline, but make sure you have yq tool installed to do the thing. Or you can use any tool to do the same.
yq w -i values.yaml envName dev
This whole process replaces your config file with API_URL
to dev.mycompany.io
as I gave dev
in yq
tool.
But if you are confused with using yq tool or something, you can have multiple values files for each environment separately and make changes to helm install step in your deployment.
helm install ./path --values ./dev-values.yaml
But your configmap should look something like this if you have multiple values files and operating which values to pick from helm install
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.definitionName }}-{{ .Values.envName }}-configmap
namespace: {{ .Values.Namespace }}
data:
API_URL: '{{ .Values.API_URL }}'
Well this is one way of doing things.
2.Manipulating Dockerfile
You can also do this with dockerfile, something like this step in your dockerfile would replace the value of the file.
RUN sed -i "s/env/dev.mycompany.io/" /app/config.js
But as the url is unique to each env you can take values using ARG
ARG url
RUN sed -i "s/env/${url}" /app/config.js
And during your build pipeline you need to have a task for docker build and under that pass the value of url
as an argument you can see that arguments column in your task add this --build-arg url=dev.mycompany.io
This is another way to add values to your config.js
file, but it also adds four(based on four envs) docker builds. And so your agents would be busy building four different images for each git commit and queuing up others builds. If you feel that command is not working in Dockerfile add RUN cat /app/config.js
in your docker file, and you can debug what's happening and check if the values are updated as you change.
Again it's debatable which is good and bad, but I personally prefer first one due to number of commits I make in an hour, but if the url changes you need not change your codebase you just need to update the docker build in your pipeline. So kinda debatable.
There are other ways to do this as well. But these two are somewhat simplest to achieve.
Hope this is helpful.
In addition to the method of @BinaryBullet provided, you can try with another way that it can make use of one Azure DevOps task to replace the content of config.js file before this .js
is applied with kubernetes.
The use of this task is very simple.
Step1:
Configure yourself Token prefix
:
Step2:
Then apply this Token prefix
into your config.js
file where you want it be replaced by various values dynamically:
Step3:
Do not forget to specify the value you want it passed to config.js
into Variables tab:
Note: The variable name
must same with the one you configured in config.js
. During the task running, it will inject the corresponding variable value into the config.js
file based on the replace format #{}#
and same variable name
.
For example, I use apiurl
in my second screenshots, so here I add one variable apiurl
and give it value which I want this value can be replaced into this config.js
file at build time.
Build result:
This Replace token task do not has limitation. It can be used in various type file. See my another similar answer: #1.
Hope this is the one which can help you achieve your expectation.