28

This is my second post about this particular issue. I've since deleted that question because I've found a better way to explain what exactly I'd like to do.

Essentially, I'd like to pass command line arguments to docker-compose up and set them as environment variables in my Vue.js web application. The goal is to be able to change the environment variables without rebuilding the container every time.

I'm running into several issues with this. Here are my docker files:

Dockerfile for Vue.js application.

FROM node:latest as build-stage
WORKDIR /app

# Environment variable.
ENV VUE_APP_FOO=FOO

COPY package*.json ./
RUN npm install
COPY ./ .
RUN npm run build

FROM nginx as production-stage
RUN mkdir /app
COPY --from=build-stage /app/dist /app
COPY nginx.conf /etc/nginx/nginx.conf

VUE_APP_FOO is stored and accessible via Node's process.env objected and seems to be passed in at build time.

And my docker-compose.yml:

version: '3.5'

services:
    ms-sql-server:
        image: mcr.microsoft.com/mssql/server:2017-latest-ubuntu
        ports: 
            - "1430:1433"
    api:
        image: # omitted (pulled from url)
        restart: always
        depends_on: 
            - ms-sql-server
        environment:
            DBServer: "ms-sql-server"
        ports:
            - "50726:80"
    client:
        image: # omitted(pulled from url)
        restart: always
        environment:
            - VUE_APP_BAR="BAR"
        depends_on: 
            - api
        ports:
            - "8080:80"

When I ssh into the client container with docker exec -it <container_name> /bin/bash, the VUE_APP_BAR variable is present with the value "BAR". But the variable is not stored in the process.env object in my Vue application. It seems like something odd is happening with Node and it's environmental variables. It's like it's ignoring the container environment.

Is there anyway for me to access the container level variables set in docker-compose.yml inside my Vue.js application? Furthermore, is there anyway to pass those variables as arguments with docker-compose up? Let me know if you need any clarification/more information.

2
  • What is serving your Vue app? Nginx? Commented Jan 13, 2020 at 19:11
  • Yes, it's Nginx. Commented Jan 13, 2020 at 19:14

7 Answers 7

14

So I figured out how to do this in sort of a hacky way that works perfectly for my use case. A quick review of what I wanted to do: Be able to pass in environment variables via a docker-compose file to a Vue.js application to allow different team members to test different development APIs depending on their assignment(localhost if running the server locally, api-dev, api-staging, api-prod).

The first step is to declare your variables in a JS file inside your VueJS project (it can be defined anywhere) formatted like this:

export const serverURL = 'VUE_APP_SERVER_URL'

Quick note about the value of this string: it has to be completely unique to your entire project. If there is any other string or variable name in your application that matches it, it will get replaced with the docker environment variable we pass using this method.

Next we have to go over to our docker-compose.yml and declare our environment variable there:

docker-compose.yml

your_vuejs_client:
    build: /path/to/vuejs-app
    restart: always
    environment: 
        VUE_APP_SERVER_URL: ${SERVER_URL}
    ports:
        - "8080:80"

Now when you run docker-compose up in your terminal, you should see something like this: WARNING: The SERVER_URL variable is not set. Defaulting to a blank string.

After we have our docker-compose setup properly, we need to create a entrypoint script in the VueJS application to run before the app is launched via nginx. To do this, navigate back to your VueJS directory and run touch entrypoint.sh to create a blank bash script. Open that up and this is what I have in mine:

entrypoint.sh

#!/bin/sh

ROOT_DIR=/usr/share/nginx/html

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_SERVER_URL|'${VUE_APP_SERVER_URL}'|g' $file

done

sed -i 's|VUE_APP_SERVER_URL|'${VUE_APP_SERVER_URL}'|g' $file This line transverses your entire application for the string 'VUE_APP_SERVER_URL' and replaces it with the environment variable from docker-compose.yml.

Finally we need to add some lines to our VueJS application Dockerfile to tell it to run the entrypoint script we just created before nginx is started. So right before the CMD ["nginx", "-g", "daemon off;"] line in your Dockerfile, add the lines below:

VueJS Dockerfile

# Copy entrypoint script as /entrypoint.sh
COPY ./entrypoint.sh /entrypoint.sh

# Grant Linux permissions and run entrypoint script
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

After that, running docker-compose run -e SERVER_URL=yourserverapi.com/api, the serverURL constant we set in a JS file at the beginning will be replaced with whatever you supply in the docker-compose command. This was a pain to finally get working, but I hope this helps out anyone facing similar troubles. The great thing is that you can add as many environment variables as you want, just add more lines to the entrypoint.sh file and define them in the Vue.js application and your docker-compose file. Some of the ones I've used are providing a different endpoint for the USPS API depending on whether you're running locally or hosted in the cloud, providing different Maps API keys based on whether the instance is running in production or development, etc. etc.

I really hope this helps someone out, let me know if anyone has any questions and I can hopefully be of some help.

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

3 Comments

Hi @jrend, will this work in google cloud run environment variable?
@Ricardo Currently we're running with Azure, but as long as you can pass environment variables to the docker container, it will work fine.
There is another way to do it. stackoverflow.com/questions/53010064/…
4

The client app runs on a web browser, but environment variables on are on the server. The client needs a way to obtain the environment variable value from the server.

To accomplish that, you have several options, including:

  1. Leverage Nginx to serve the environment variable itself for this using an approach like one of these: nginx: use environment variables. This approach may be quick, more dynamic or more static depending on your needs, maybe less formal and elegant. Or

  2. Implement a server API (Node.js?) that reads the environment variable and returns it to the client over an AJAX call. This approach is elegant, dynamic, API-centric. Or

  3. Lastly if the environment variable is static per nginx instance per deployment, you could build the static assets of the Vue app during deployment and hard-code the environment variable right there in the static assets. This approach is somewhat elegant but does pollute client code with server details and is somewhat static (can only change on deployment).

1 Comment

I found a hacky way of doing this, will post as a solution later. The purpose was to be able to specify the base API url via docker-compose so our testers could pass in values without having to rebuild each time.
1

As i posted here https://stackoverflow.com/a/63097312/4905563, I have developed a package that could help.

Try with npm install jvjr-docker-env take a look to README.md to see some examples of use.

Comments

1

Even though the question title says how to consume environment variables from vue.js side, the questioner's goal is to be able to configure backend api endpoint dynamically without rebuilding docker image.

I achieved it by using reverse proxy.

  1. For dev run, configure reverse proxy on vue.config.js, which is consumed by vue-cli web server.
  2. For nginx run, configure reverse proxy on nginx.conf. You can use nginx template to read environment variables.

This approach also eliminates the need for CORS configuration on web-api server side, since web api is called from vue app's web server, not from the browser directly.

More thorough working sample can be found on this commit.

vue.config.js:

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://host.docker.internal:5000',
      },
    },
  },
};

nginx.conf:

...
http {
  ...
  include /etc/nginx/conf.d/*.conf;
}

nginx.default.conf.template:

  server {
    listen       80;
...
    location /api {
      proxy_pass ${WEBAPI_ENDPOINT};
    }
  }

Dockerfile:

...
COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.default.conf.template /etc/nginx/templates/default.conf.template

Comments

1

Add a static config.js script in index.html. This file is not processed by Webpack, but included verbatim. Use your docker-compose file or kubernetes manifest or AWS ECS task config or similar to override that file at run time.

For example, in my Vue project:

public/config.js

// Run-time configuration. Override this in e.g. your Dockerfile, kubernetes pod or AWS ECS Task.
// Use only very simple, browser-compatible JS.
window.voxVivaConfig = {};

public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- ... -->

    <!-- Allow injection of run-time config -->
    <script async src="config.js"></script>
    
    <!-- ... -->
</head>
<body>
<div id="app" aria-busy="true">
    <noscript><p>This app requires JavaScript.</p></noscript>
</div>
</body>
</html>

src/config.js

function getRunTimeConfig() {
  if (typeof window.voxVivaConfig === "object") {
    return window.voxVivaConfig || {};
  }

  return {};
}

export default Object.freeze({
  appTitle: "My App",

  backEndBaseUrl: process.env.VUE_APP_BACK_END_BASEURL || "https://example.com",
  whatever: process.env.VUE_APP_WHATEVER || "",

  /**
   * Allow config specified at run time to override everything above.
   */
  ...getRunTimeConfig(),
});

Advantages

This puts all config in one place, and lets you choose which config values should be specified at compile time, build time or run time, as you see fit.

2 Comments

this looks promising but it's not clear how you override or inject the variables from the docker-compose.yml, could you elaborate?
For docker, see docs.docker.com/storage/bind-mounts . For docker-compose, see docs.docker.com/compose/compose-file/#volumes . For k8s, see kubernetes.io/docs/concepts/configuration/configmap . Bottom line: You mount a custom public/config.js file over the default one.
1

Overall the first answer is working I found a cleaner and easier solution.

  1. Define env variables as build args in the Dockerfile
# build stage
FROM node:lts-alpine as build-stage

ARG VITE_API_URL # Declare them here
ARG VITE_API_KEY # and here

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
  1. Load the env variables in vite.config.js
import { fileURLToPath, URL } from 'node:url'

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig((mode) => {
  
  // this is what you have to add to make it work
  const env = loadEnv(mode, process.cwd(), '') 
  
  return {
    plugins: [
      vue(),
    ],
    server: {
      watch: {
        usePolling: true
      }
    },
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      }
    }
  }
})
  1. Build the docker image with passing build args as
docker build -t myimg:mytag \
--build-arg VITE_API_KEY=blabla \
--build-arg VITE_API_URL=fukyalifeBIMBOM .
  1. Have a pleasant day because it works as charm :)

1 Comment

Here is how to add build-args to docker-compose stackoverflow.com/questions/50734271/…
1

Maybe it isn't relevant after 4 years, but I came up with another solution respecting the immutability principle.

To use a docker-compose.yml file as the following:

version: "3.8"
services:
  api:
    image: clockedwtc/calculations-api:latest
    container_name: api-2
    ports:
      - "3001:8080"
  client:
    # image: clockedwtc/client:latest
    image: client-image-prod
    container_name: client-2
    environment:
      - VITE_API_URL=http://localhost:3001
    depends_on:
      - api
    ports:
      - "8080:8080"

I added an endpoint on /etc/nginx/conf.d/default.conf (which is a nginx config file) to return environment variables.

server {
    listen       8080;
    server_name  localhost;
    [...]
    location /env {
        default_type application/json;
        return 200 '{"VITE_API_URL":"$VITE_API_URL"}';
    }
}

With this configuration requesting the route "/env" (on the same URL as the front-end app) the environment variable "VITE_API_URL" will be returned, which is defined in docker-compose.yml.

Finally, to make it possible to use this variable, in the front-end's code there's a function similar to this:

const getApiUrlEnvVar = async (): Promise<string> => {
  const response = await axios.get("/env");
  return response.data['VITE_API_URL'] ?? null;
};

The complete files and source code are available at: https://github.com/clocked-app/client

Edit: The nginx endpoint idea came from this answer

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.