Search code examples
dockervue.jsnginxenvironment-variablesvue-cli-3

VueCLI3 app (nginx/docker) use environment specific variables


How to externalize and consume environment variables from a Vue App:

  • Created with VueCLI3
  • Deployed in a docker container
  • Using NGINX

Some details:

The project is built once and deployed to test and live environments. So, I want to externalize some variables which change through the environments (like URLs to call, domains, usernames etc.). The classical usage with .env file variations with VUE_APP_ prefixed does not help this issue as their values are injected in the code during the build stage: They are not variables once it is built.

Trying it out, I have found a blog post making use of dotenv and some extra configuration; but I could not put it together with the configuration in this VueCLI 3 official guide. The solution does not need to adopt a similar approach though, I am just trying to make a way out.

Probably not a useful information, but I am planning to define those environment variables in Config Maps in Kubernetes configuration.


Solution

  • I think I've accomplished to overcome this case. I am leaving the resolution here.

    1. Define your environment-specific environment variables in .env.development (for development purposes) and add them also to the Pod configuration with correxponding values.

    2. Add a configuration.js file somewhere in your Vue project source folder. It would act as a wrapper for determining whether the runtime is development (local) or production (container). It is like the one shown here, but importing/configuring dotenv is not required:

      export default class Configuration {
        static get EnvConfig () {
          return {
            envKey1: '$ENV_KEY_1',
            envKey2: '$ENV_KEY_2'
          }
        }
      
        static value (key) {
          // If the key does not exist in the EnvConfig object of the class, return null
          if (!this.EnvConfig.hasOwnProperty(key)) {
            console.error(`Configuration: There is no key named "${key}". Please add it in Configuration class.`)
            return
          }
      
          // Get the value
          const value = this.EnvConfig[key]
      
          // If the value is null, return
          if (!value) {
            console.error(`Configuration: Value for "${key}" is not defined`)
            return
          }
      
          if (!value.startsWith('$VUE_APP_')) {
            // value was already replaced, it seems we are in production (containerized).
            return value
          }
      
          // value was not replaced, it seems we are in development.
          const envName = value.substr(1) // Remove $ and get current value from process.env
          const envValue = process.env[envName]
      
          if (!envValue) {
            console.error(`Configuration: Environment variable "${envName}" is not defined`)
            return
          }
      
          return envValue
        }
      }
      
      
    3. Create an entrypoint.sh. With some modification, it would look like follows:

      #!/bin/bash
      
      function join_by { local IFS="$1"; shift; echo "$*"; }
      
      # Find vue env vars
      vars=$(env | grep VUE_APP_ | awk -F = '{print "$"$1}')
      vars=$(join_by ',' $vars)
      echo "Found variables $vars"
      
      for file in /app/js/app.*;
      do
        echo "Processing $file ...";
      
        # Use the existing JS file as template
        cp $file $file.tmpl
        envsubst "$vars" < $file.tmpl > $file
        rm $file.tmpl
      done
      
      nginx -g 'daemon off;'
      
    4. In your Dockerfile, add a CMD for running this entrypoint.sh script above as a bootstrapping script during container creation. So that, every time you start a container, it will get the environment variables from the pod configuration and inject it to the Configuration class shown in Step 2.

      # build stage
      FROM node:lts-alpine as build-stage
      
      # make the 'app' folder the current working directory
      WORKDIR /app
      
      # Copy package*.json and install dependencies in a separaate step to enable caching
      COPY package*.json ./
      RUN npm install
      
      # copy project files and folders to the current working directory
      COPY ./ .
      
      # install dependencies and build app for production with minification
      RUN npm run build
      
      # Production stage
      FROM nginx as production-stage
      
      RUN mkdir /app
      
      # copy 'dist' content from the previous stage i.e. build
      COPY --from=build-stage /app/dist /app
      
      # copy nginx configuration
      COPY nginx.conf /etc/nginx/nginx.conf
      
      # Copy the bootstrapping script to inject environment-specific values and pass it as argument current to entrypoint
      COPY entrypoint.sh entrypoint.sh
      
      # Make the file executable
      RUN chmod +x ./entrypoint.sh
      
      CMD ["./entrypoint.sh"]
      
      

    Finally, instead of process.env use our wrapper configuration class like Configuration.value('envKey1'). And voila!