Dynamically set Angular Environment Variables in Docker

Angular's environment variables are baked into the application on every build and not meant to be changed afterwards by default. This requires a separate build for every environment. In the world of containers it is common, to configure the app via environment variables. Here is how to achieve that.

Dynamically set Angular Environment Variables in Docker

Angular's environment variables are baked into the application on every build and not meant to be changed afterwards by default. This requires a separate build for every environment. In the world of containers it is common, to configure the app via environment variables. Here is how to achieve that.

According to the Twelve-Factor App, configuration should be stored in the environment. By default, this is not possible with Angular's built-in environment variables. So we need to feed them from an external source.

Angular Environment from an external source

The default environment.ts is getting cross-compiled into a nearly un-editable JavaScript file on every build and is not meant to change afterwards. One way around that, can be externalizing the configuration from the cross-compiles app bundle.

In the /assets folder, create a new file called env.js with the following content:

(function(window) {
  window["env"] = window["env"] || {};

  // Environment variables
  window["env"]["apiUrl"] = "https://api.myapp.com";
  window["env"]["debug"] = true;
})(this);

This JavaScript function defines our future environment variables. As is is part of the /assets folder, it won't be cross-compiles but simply copied to the /dist directory and can be edited in clear text later.

Call the function at application startup by adding it to the index.html file:

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

    <!-- Load environment variables -->
    <script src="assets/env.js"></script>
  </head>
  <body>
    <app-root></app-root>
  </body>
</html>

Now open Angular's environemnt.ts file and feed it's values with the ones from the env.js file:

export const environment = {
  production: false,
  apiUrl: window["env"]["apiUrl"] || "default",
  debug: window["env"]["debug"] || false
};

What we achieved by now is that the environments.ts values are coming from an external source. In our case, this source is env.js.

Create an environment variable template

Now that Angular's environment variables are provided by the external env.js file, we need a way to dynamically set the values in this file. For that, we create a new env.template.js file next to the env.js file in the /assets folder. The content is an exact copy of env.js, just with placeholder variables.

(function(window) {
  window.env = window.env || {};

  // Environment variables
  window["env"]["apiUrl"] = "${API_URL}";
  window["env"]["debug"] = "${DEBUG}";
})(this);

These ${PLACEHOLDER} variables can now be overwritten in your CI/CD pipeline or Docker image with the envsubst command. This command can create a new env.js file based one the template and replace the placeholders with environment variables.

# Set environment variable
export API_URL="https://new-api.myapp.com";

# Replace variables in env.js
envsubst < assets/env.template.js > assets/env.js

Replace placeholders inside a Dockerfile

Of course, we can use the same envsubst command when booting up a Docker container. The last line of the following sample Dockerfile shows how to replace the env.js placeholder with values from actual environment variables on every startup.

#################
# Build the app #
#################
FROM node:12-alpine as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm install -g @angular/cli
RUN ng build --configuration production --output-path=/dist

################
# Run in NGINX #
################
FROM nginx:alpine
COPY --from=build /dist /usr/share/nginx/html

# When the container starts, replace the env.js with values from environment variables
CMD ["/bin/sh",  "-c",  "envsubst < /usr/share/nginx/html/assets/env.template.js > /usr/share/nginx/html/assets/env.js && exec nginx -g 'daemon off;'"]

A configuration variable like API_URL can now be set using an environment variable with the container.

docker run --env API_URL="https://demo-api.myapp.com" my-container:latest

☝️ Advertisement Block: I will buy myself a pizza every time I make enough money with these ads to do so. So please feed a hungry developer and consider disabling your Ad Blocker.