Angular has its way of handling different target environments, through angular environments files – which you can learn more about here. The issue with this approach is that it requires you to commit keys and configuration to your version control. This is not a big problem since it’s not advisable have any sensitive data in your angular source code.

However, I find it important to keep those keys and configurations outside the source control. This makes it easier to manage keys and configurations for different environments, without requiring you to modify the source code to make any changes. As you might have guessed from the title of this post, we will be using docker and OS Environment variables to pass some keys to Angular CLI, through webpack. Why OS Environment Variables? Mainly because they are platform agnostic, you can use them with the platforms of your choice. And also they are very easy to work with.

How it works?

The basic concept here is rather simple. For the purpose of this post, we are going to set a few environment variables, inside our dockerfile. Then, Webpack is going to read them, and pass them to our angular app during build time. Ideally though, you don’t want to set the variables in your dockerfile, but rather somewhere else like a .env file. Then, during docker build, retrieve the configurations and set them to environment variables.

If they are stored somewhere else, like let’s say an S3 Bucket, then you can perform the extra step of fetching them and setting them up. This can be achieved easily via multi-stage build in dockerfile. To build our angular app for deployment using docker, we are going to use docker multi-stage build.

This will result into an image which is smaller – which reduces our attack surface area. I won’t go into details about this, but I wrote another post explaining everything which you can find here. To make everything easy, we will use OS Environment Variables to configure production environment only – environment.prod.ts.

This is because, for dev environment, I am assuming that you will be relying mainly on ng serve, which is out of scope for this post. That will be covered in a different post latter this week. So, without further ado, let’s get started:

Getting Started

Assuming you already have your Angular project setup, First, we are going to install @angular-builders/custom-webpack as a dev dependency. In a nutshell, this allows us to customize build configuration without ejecting our angular project.

yarn add --dev @angular-builders/custom-webpack

OR

npm install -D @angular-builders/custom-webpack

Configuring Angular

Next, we are going to modify our angular configurations – angular.json – to use the new builder(@angular-builders/custom-webpack:browser) instead of the default angular one (@angular-devkit/build-angular:browser). And then, under options, we are also going to add a customWebpackConfig option with a path pointing to custom-webpack.config.js file as shown below.

"projects": {
    "name-of-the-project": {
      //...
      "architect": {
        "build": {
          "builder":"@angular-builders/custom-webpack:browser"_,
          "options": {
            "customWebpackConfig": {
                "path":"custom-webpack.config.js"
            },
            //...
        //...
    }
}

NB: We have changed the default angular builder from @angular-devkit/build-angular:browser to @angular-builders/custom-webpack:browser.

Then, we are going to create our custom-webpack.config.js file – which we referenced above – at the root of our Angular Workspace. And then add the following content, to retrieve OS Environment Variables and pass them to our builder.

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      $ENV: {
        ENVIRONMENT: JSON.stringify(process.env.ENVIRONMENT),
        SomeAPIKey: JSON.stringify(process.env.SomeAPIKey),
        SomeOtherAPIKey: JSON.stringify(process.env.SomeOtherAPIKey)
      }
    })
  ]
};

In a nutshell, we are creating a webpack plugin. And then, we are reading the OS environment variables and passing them to Angular, via webpack. We are grouping all our environment variables, under $ENV variable, so that we can simply access them like this: $ENV.ENVIRONMENT or $ENV.SomeAPIKey inside our angular app. In this case, we have 3 environment variables: Environment, SomeAPIKey and SomeOtherAPIKey.

Using OS Environment Variables in Angular

First, we are going to add some typings, so that we can reference to our $ENV variable from webpack above within our Angular app. First, let’s create a typing.d.ts file inside your src directory at the root of your workspace and add the following content.

declare var $ENV: Env;

interface Env {
  ENVIRONMENT: string;
  SomeAPIKey: string;
  SomeOtherAPIKey: string;
}

In a nutshell, we are declaring a global variable $ENV of type Env interface. Inside the Env interface, we are adding fields that we expect to be passed by our webpack plugin that we created in the previous step.

NB: You can rename $ENV to a variable of your choice. Make sure to change typings.d.ts and custom-webpack.config.js file appropriately.

Then, inside the our environment.prod.ts, we need to read the OS environment variables passed to our application, into variables we can use, within our angular application. This can be done like this: $ENV.ENVIRONMENT – so that our environment.prod.ts would like this:

export const environment = {
  production: true,
  environment: $ENV.ENVIRONMENT,
  APIKeys: {
    SomeAPIKey: $ENV.SomeAPIKey,
    SomeOtherAPIKey: $ENV.SomeOtherAPIKey
  }
};

Then, inside our environment.ts file – the one use when using ng serve or build without production – we need to add the same keys but with static variables.

export const environment = {
  production: false,
  environment: 'development',
  APIKeys: {
    SomeAPIKey: 'DEV API Key',
    SomeOtherAPIKey: 'DEV API Key 2'
  }
};

Now, your OS Environment variables are accessible throughout your application. They can be used just like any other angular environment variables inside your application.

export class AppComponent {
  public environment = environment.environment;
  public SomeAPIKey = environment.APIKeys.SomeAPIKey;
  public SomeOtherAPIKey = environment.APIKeys.SomeOtherAPIKey;
}

The Dockerfile

Inside our dockerfile, we are going to be setting environment variables which will be picked up by webpack. This environment variables need to be set at the builder stage of our docker multi-stage build.

# STAGE 1
FROM node:10.15.0 as builder
...
# SET ENVIRONMENT VARIABLES
ENV ENVIRONMENT=production1
ENV SomeAPIKey="This is not an API Key"
ENV SomeOtherAPIKey="This is not another API Key"
...
RUN yarn global add @angular/cli@latest
...
RUN yarn install
...
RUN ng build --prod
...
# STAGE 2
...

NB: You can find the complete dockerfile file here, with comments on each step.

Like I said (wrote I guess) at the beginning, the values for this environment variables can come from a variety of sources. You can use docker-compose to pass a .env file, which you can learn more about here. This can also be retrieved from a cloud storage service AWS S3 or GCP Cloud Storage either in the builder stage or another stage prior to builder.

Source Code

You can find the source code for this post here.