59

How can I access environment variables in Vue, that are passed to the container at runtime and not during the build?

Stack is as follows:

  • VueCLI 3.0.5
  • Docker
  • Kubernetes

There are suggested solutions on stackoverflow and elsewhere to use .env file to pass variables (and using mode) but that's at build-time and gets baked into the docker image.

I would like to pass the variable into Vue at run-time as follows:

  • Create Kubernetes ConfigMap (I get this right)
  • Pass ConfigMap value into K8s pod env variable when running deployment yaml file (I get this right)
  • Read from env variable created above eg. VUE_APP_MyURL and do something with that value in my Vue App (I DO NOT get this right)

I've tried the following in helloworld.vue:

<template>
<div>{{displayURL}}
  <p>Hello World</p>
</div>
</template>
<script>
export default {  
    data() {
        return {
            displayURL: ""
        }
    },
    mounted() {
        console.log("check 1")
        this.displayURL=process.env.VUE_APP_ENV_MyURL
        console.log(process.env.VUE_APP_ENV_MyURL)
        console.log("check 3")
    }
}
</script>

I get back "undefined" in the console log and nothing showing on the helloworld page.

I've also tried passing the value into a vue.config file and reading it from there. Same "undefined" result in console.log

<template>
<div>{{displayURL}}
  <p>Hello World</p>
</div>
</template>
<script>
const vueconfig = require('../../vue.config');
export default {  
    data() {
        return {
            displayURL: ""
        }
    },
    mounted() {
        console.log("check 1")
        this.displayURL=vueconfig.VUE_APP_MyURL
        console.log(vueconfig.VUE_APP_MyURL)
        console.log("check 3")
    }
}
</script>

With vue.config looking like this:

module.exports = {
    VUE_APP_MyURL: process.env.VUE_APP_ENV_MyURL
}

If I hardcode a value into VUE_APP_MyURL in the vue.config file it shows successfully on the helloworld page.

VUE_APP_ENV_MyURL is successfully populated with the correct value when I interrogate it: kubectl describe pod

process.env.VUE_APP_MyURL doesn't seem to successfully retrieve the value.

For what it is worth... I am able to use process.env.VUE_APP_3rdURL successfully to pass values into a Node.js app at runtime.

4
  • 1
    Ae you building the vue code into static html/js or running it as a dev in docker? Commented Oct 26, 2018 at 14:00
  • 1
    FWIW as long as you have access to the file containing the env variables, you may use the FileReader API to parse it Commented Oct 26, 2018 at 15:14
  • Thanks @varcorb I'm building the vue code into static html/js Commented Oct 29, 2018 at 8:37
  • per the exact thing you are asking, Hendrik Malkows answer does this, and it was super useful that he detailed how to add it. Please consider marking it the correct answer! To be clear, i have a k8s configMap, that gets injected at runtime, and allows me to pass a base URL over the top of the one from process.env Commented Feb 1, 2020 at 1:15

8 Answers 8

49

Create a file config.js with your desired configuration. We will use that later to create a config map that we deploy to Kubernetes. Put it into your your Vue.js project where your other JavaScript files are. Although we will exclude it later from minification, it is useful to have it there so that IDE tooling works with it.

const config = (() => {
  return {
    "VUE_APP_ENV_MyURL": "...",
  };
})();

Now make sure that your script is excluded from minification. To do that, create a file vue.config.js with the following content that preserves our config file.

const path = require("path");
module.exports = {
  publicPath: '/',
  configureWebpack: {
    module: {
      rules: [
        {
          test: /config.*config\.js$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: 'config.js'
              },
            }
          ]
        }
      ]
    }
  }
}

In your index.html, add a script block to load the config file manually. Note that the config file won't be there as we just excluded it. Later, we will mount it from a ConfigMap into our container. In this example, we assume that we will mount it into the same directory as our HTML document.

<script src="<%= BASE_URL %>config.js"></script>

Change your code to use our runtime config:

this.displayURL = config.VUE_APP_ENV_MyURL || process.env.VUE_APP_ENV_MyURL 

In Kubernetes, create a config map that uses the content your config file. Of course, you wanna read the content from your config file.

apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  config.js: |
    var config = (() => {
      return {
        "VUE_APP_ENV_MyURL": "...",
      };
    })();

Reference the config map in your deployment. This mounts the config map as a file into your container. The mountPath Already contains our minified index.html. We mount the config file that we referenced before.

apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  ...
  template:
    ...
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: ...
      containers:
        - ...
          volumeMounts:
                - name: config-volume
                  mountPath: /usr/share/nginx/html/config.js
                  subPath: config.js

Now you can access the config file at <Base URL>/config.js and you should see the exact content that you put into the ConfigMap entry. Your HTML document loads that config map as it loads the rest of your minified Vue.js code. Voila!

Sign up to request clarification or add additional context in comments.

7 Comments

where should I place the config.js and how to I import the config when I am using it in the code?
I just added some more detailed explanation. does it help?
@HendrikMHalkow Could you you add a tree of what your project structure looks like?
Hi, does this sulotion work if the config object needs to be referred in a Typescript file? Thank you.
Can you please add some more detailed explanation or a link to a project structure how it looks, also what if I need to load the same flag into index.html?
|
28

I am adding my working solution here, for those who are still having trouble. I do think that @Hendrik M Halkow 's answer is more elegant, though I couldn't manage to solve it using that, simply just because of my lack of expertise in webpack and Vue.I just couldn't figure out where to put the config file and how to refer it.

My approach is to make use of the environment variables with constants (dummy values) to build it for production, then replace that constants in the image using a custom entrypoint script. The solution goes like this.

I have encapsulated all configs into one file called app.config.js

export const clientId = process.env.VUE_APP_CLIENT_ID
export const baseURL = process.env.VUE_APP_API_BASE_URL

export default {
  clientId,
  baseURL,
}

This is used in the project just by looking up the value from config file.

import { baseURL } from '@/app.config';

Then I am using standard .env.[profile] files to set environment variables. e.g. the .env.development

VUE_APP_API_BASE_URL=http://localhost:8085/radar-upload
VUE_APP_CLIENT_ID=test-client

Then for production I set string constants as values. e.g. the .env.production

VUE_APP_API_BASE_URL=VUE_APP_API_BASE_URL
VUE_APP_CLIENT_ID=VUE_APP_CLIENT_ID

Please not here the value can be any unique string. Just to keep the readability easier, I am just replacing the environment variable name as the value. This will just get compiled and bundled similar to development mode.

In my Dockerfile, I add an entrypoint that can read those constants and replace it will environment variable values.

My Dockerfile looks like this (this is pretty standard)

FROM node:10.16.3-alpine as builder

RUN mkdir /app
WORKDIR /app

COPY package*.json /app/
RUN npm install

COPY . /app/

RUN npm run build --prod

FROM nginx:1.17.3-alpine

# add init script
COPY ./docker/nginx.conf /etc/nginx/nginx.conf

WORKDIR /usr/share/nginx/html

COPY --from=builder /app/dist/ .

COPY ./docker/entrypoint.sh /entrypoint.sh

# expose internal port:80 and run init.sh
EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

Then create a ./docker/entrypoint.sh file as below.

#!/bin/sh

ROOT_DIR=/usr/share/nginx/html

# Replace env vars in JavaScript files
echo "Replacing env constants in JS"
for file in $ROOT_DIR/js/app.*.js* $ROOT_DIR/index.html $ROOT_DIR/precache-manifest*.js;
do
  echo "Processing $file ...";

  sed -i 's|VUE_APP_API_BASE_URL|'${VUE_APP_API_BASE_URL}'|g' $file 
  sed -i 's|VUE_APP_CLIENT_ID|'${VUE_APP_CLIENT_ID}'|g' $file

done

echo "Starting Nginx"
nginx -g 'daemon off;'

This enables me to have runtime configurable image that I can run on many environments. I know it is a bit of a hack. But have seen many people do it this way.

Hope this helps someone.

7 Comments

Hi @NehaM, I just added some more detailed explanation to my proposed solution. I hope it helps you. I recommend to avoid scripting your entry point as it doesn't work with distroless images. Have a look at github.com/SDA-SE/nginx – an NGINX image that is less than 6MB. If you can't avoid scripting, put an exec before the last command so that the shell process will be replaced by your nginx process.
Thanks for the suggestion and improvising the answer. I am still using the solution I came up with. I have added small improvements to the code.
I think this one is the most elegant. I'd only like it to be more dynamic so we don't need to repeat the variables too much but not sure we can do this while staying readable
The value name should be different than the key name, otherwise it will not work. In .env.production, it should be VUE_APP_API_BASE_URL=VUE_APP_API_BASE_URL_value
this is the best practice used by Gravitee.io frontend app, have a look at github.com/gravitee-io/gravitee-docker/blob/master/images/…
|
11

Create config file

In public folder: public/config.js

const config = (() => {
  return {
    "VUE_CONFIG_APP_API": "...",
  };
})();

Update index.html

Update public/index.html to contain following at the end of head:

  <!-- docker configurable variables -->
  <script src="<%= BASE_URL %>config.js"></script>

There is no need to update vue.config.js as we are using the public folder for configuration.

ESLint

ESLint would give us error of usage of undefined variable. Therefore we define global variable in .eslintrc.js file:

  globals: {
    config: "readable",
  },

Usage

Eg. in the store src/store/user.js

export const actions = {
  async LoadUsers({ dispatch }) {
    return await dispatch(
      "axios/get",
      {
        url: config.VUE_CONFIG_APP_API + "User/List",
      },
      { root: true }
    );
  },
...

K8S ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: fe-config
  namespace: ...
data:
  config.js: |
    var config = (() => {
      return {
        "VUE_CONFIG_APP_API": "...",
      };
    })();

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  ...
  template:
    ...
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: fe-config
      containers:
        - ...
          volumeMounts:
                - name: config-volume
                  mountPath: /usr/share/nginx/html/config.js
                  subPath: config.js

5 Comments

why not just define const config = {xxx: "..."} in config.js? thanks for your replay.
because config is hardcoded and you do not have option to modify it at runtime for example in kubernetes.. you have to load it post build
Thanks for your replay! My question is not so clear. I mean, why wrap object in an anonymous function and return it, why not just assigns an object to 'config'?
For deploying on multiple servers with different api endpoints this would be the best simple solution, no rebuild required for every servers, just change one line and go.
I tried this and got ReferenceError: config is not defined in vue.config.js when I tried yarn serve
6

I had the same problem in my current project and found out that it is not possible to access environment variables at runtime at the moment so I end up with the solution of creating .env files or local environment variables that, as you said, are used at the build time.

2 Comments

The author of this post explains that it doesn't make sense to try pass a variable into static html.js code. I finally get it! reddit.com/r/vuejs/comments/8sbosc/…
build time not runtime .. is a violation of release engineering principles... Anyway, create-react-app is the best for React .. from that perspective
5

If you are using VueJs3 + Vite3 + TypeScript, you can do this:

create app.config.ts (not JS)

export const clientId = import.meta.env.VITE_CLIENT_ID
export const baseURL = import.meta.env.VITE_API_BASE_URL

export default {
  clientId,
  baseURL,
}

Replace values in assets subdir: (improved shell script)

#!/bin/sh
# @see https://stackoverflow.com/questions/18185305/storing-bash-output-into-a-variable-using-eval
ROOT_DIR=/usr/share/nginx/html
          
# Replace env vars in JavaScript files
echo "Replacing env constants in JS"

keys="VITE_CLIENT_ID
VITE_API_BASE_URL"

for file in $ROOT_DIR/assets/index*.js* ;
do
  echo "Processing $file ...";
  for key in $keys
  do
    value=$(eval echo \$$key)
    echo "replace $key by $value"
    sed -i 's#'"$key"'#'"$value"'#g' $file
  done
done

echo "Starting Nginx"
nginx -g 'daemon off;'

In the Dockerfile don't forget "RUN chmod u+x"

# build
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./docker/entrypoint.sh /entrypoint.sh

COPY ./docker/entrypoint.sh /usr/local/bin/
RUN chmod u+x /usr/local/bin/entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

then you be able to import it in any TypeScript file in the project

import { baseURL } from '@/app.config';

@see update of NehaM response: Pass environment variable into a Vue App at runtime

2 Comments

What is the index.js though? Is that the same as the index.html for me?
This is the file built, for example, VueJs+Vite will build in asset build dir : About.3ebd8e42.css, About.a0fa1f22.js, index.a144206e.css and... index.47dd29d7.js
2

I got it to work with the solution proposed by @Hendrik M Halkow.

But I stored the config.js in the static folder. By doing that, I don't have to care about not minifying the file.

Then include it like this:

<script src="<%= BASE_URL %>static/config.js"></script>

and use this volume mount configuration:

...
volumeMounts:
    - name: config-volume
      mountPath: /usr/share/nginx/html/static/config.js
      subPath: config.js

Comments

2

I recently created a set of plugins to solve this problem elegantly:

  • No global namespace pollution.
  • No import statement is required.
  • Almost zero configuration.
  • Out-of-the-box support for Vue CLI (also works with Webpack, Rollup and Vite, CSR, SSR and SSG, and unit testing tools. Powered by Unplugin and Babel).

You can access environment variables (heavily inspired by Vite) this way:

// src/index.js
console.log(`Hello, ${import.meta.env.HELLO}.`);

During production, it will be temporarily replaced with a placeholder:

// dist/index.js
console.log(`Hello, ${"__import_meta_env_placeholder__".HELLO}.`);

Finally, you can use built-in script to replace placeholders with real environment variables in your system (e.g., read environment variables in your k8s pod):

// dist/index.js
console.log(`Hello, ${{ HELLO: "import-meta-env" }.HELLO}.`);
// > Hello, import-meta-env.

You can see more info at https://iendeavor.github.io/import-meta-env/ .

And there is a Docker setup example and Vue CLI setup example.

Hope this helps someone who needs this.

Comments

1

Vue.js does not have direct access to runtime environment variables because they are baked into the app during the build process. Instead of trying to access process.env in Vue components, a better approach is to dynamically inject environment variables into a config.js file that the app can load at runtime.

Here are the steps I used to resolve this:

  • Generating a config.js file at runtime instead of embedding environment variables at build time.
  • Referencing config.js in index.html so the Vue app can access its values dynamically.
  • Updating Vue components to read from window.config instead of process.env.
  • Using an entrypoint.sh script to inject environment variables into config.js before starting the app.

I believe this tutorial provides a complete step-by-step guide on implementing this solution effectively: https://www.baeldung.com/ops/vuejs-pass-environment-variables-runtime

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.