Navigate back to the homepage

Angular environment DevOps hack no one seems to talk about

Leo Ertuna
July 6th, 2022 · 2 min read

Today I’d like to share a quick DevOps hack that allows you to overwrite Angular’s environment configs dynamically, at application startup.

One day I was building an Angular app with an unorthodox requirement - it needs to be shipped as a Docker image, but the end machine that runs the application must have the ability to pick its own backend server to connect this frontend app to. And when searching for similar problems I never found a solid way to achive this with Angular apps. Nothing came up, there was no apparent solution. So I decided to quickly hack something myself.

Disclaimer: this will involve wrapping an Angular app into a Docker container (obviously), which in my book is the correct way to deploy any application nowadays, but this might differ from your deployment practices and thus the suggested approach might not work for you.

Environments in Angular

We all know about Angular and its way of dealing with configurations / environments - it has two build configurations out of the box production and development, with corresponding two environment configs environment.prod.ts and environment.ts. Let’s draft them quickly here with 2 variables - api url and api key.

environment.prod.ts:

1export const environment = {
2 production: true,
3 apiUrl: "http://prod.example.com",
4 apiKey: "htaPrMB4SpCRWRmBvNctS9XA",
5};

environment.ts:

1export const environment = {
2 production: false,
3 apiUrl: "http://localhost:8080",
4 apiKey: "TfhS59Kb7dqmKTKxV439SqSA",
5};

Also let’s quickly build a basic page that will show us the real values of these variables:

app.component.ts:

1import {Component} from '@angular/core';
2import {environment} from "../environments/environment";
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls: ['./app.component.css']
8})
9export class AppComponent {
10 title = 'app-frontend';
11 apiUrl = environment.apiUrl;
12 apiKey = environment.apiKey
13}

app.component.html:

1<div class="container">
2 <mat-card class="env-card mx-auto my-5">
3 <mat-card-header>
4 <mat-card-title>Environment</mat-card-title>
5 <mat-card-subtitle>Debugging your environment</mat-card-subtitle>
6 </mat-card-header>
7
8 <mat-card-content>
9 <div class="container">
10 <div class="row">
11 <div class="col-3">
12 <p>API url:</p>
13 </div>
14 <div class="col">
15 <p><b>{{apiUrl}}</b></p>
16 </div>
17 </div>
18
19 <div class="row">
20 <div class="col-3">
21 <p>API key:</p>
22 </div>
23 <div class="col">
24 <p><b>{{apiKey}}</b></p>
25 </div>
26 </div>
27 </div>
28 </mat-card-content>
29 </mat-card>
30</div>

app.component.css:

1.env-card {
2 max-width: 500px;
3}

Now when we run the Angular app with ng serve it pulls the development configuration:

image 1

But when we switch to ng serve --configuration production it pulls the production configuration:

image 2

So far so good, standard behaviour as expected. This is all great and pretty straight forward, but we are limited to a predefined number of environments. Now let’s make this interesting.

Setting environment variables at runtime

When working with Docker we can always specify environment variables using docker run -e ... command. This will set the Linux environment variables in the container we’re running. This looks like exactly what we need, and ideally our Docker command will look something along the lines of:

1docker run ... -e API_URL=http://prod.another-example.com -e API_KEY=58GDFGwXsZLXxZXwh9pmdUxZ ...

To actually pass this to the Angular app we need to introduce an intermediate shell script, that will be the Docker container’s entrypoint and will perform some preprocessing before launching the angular app. Namely it will pull Linux’s environment variables and dump them into our environment.prod.ts, overwriting it with the values we received from docker run -e ... command.

With all this in mind we can build a launcher script like this:

1#!/bin/sh
2
3# Check the variables exist
4echo $API_URL
5echo $API_KEY
6
7# Check initial config
8cat /app/src/environments/environment.prod.ts
9
10# Replace config
11cat >/app/src/environments/environment.prod.ts <<EOL
12export const environment = {
13 production: true,
14 apiUrl: "${API_URL}",
15 apiKey: "${API_KEY}",
16};
17EOL
18
19# Check resuling config
20cat /app/src/environments/environment.prod.ts
21
22# Run the app
23ng serve --host 0.0.0.0 --configuration production --live-reload false --disable-host-check true

You can obviously remove the echo and cat commands that check the contents of Linux env variables and the resulting config file.

With this script done our Dockerfile becomes pretty simple:

1# Starter image as node
2FROM node:latest as node
3
4# Install angular cli in container
5RUN npm install -g @angular/cli
6
7# Copy our app's source code
8COPY . /app
9WORKDIR /app
10
11# Build our Angular app
12RUN npm install
13RUN ng build --prod
14
15# Mark shell script as executable
16RUN chmod a+x main.sh
17
18# Launch the script
19CMD './main.sh'

Now let’s build and check this out!

1docker build -t tutorial-env:latest .

Let’s try changing the api url & api key:

1docker run \
2 --name=tutorial-env \
3 --rm \
4 -ti \
5 -e API_URL=http://prod.another-example.com \
6 -e API_KEY=58GDFGwXsZLXxZXwh9pmdUxZ \
7 -p 4200:4200 \
8 tutorial-env:latest
image 3

And another attempt, just to make sure:

1docker run \
2 --name=tutorial-env \
3 --rm \
4 -ti \
5 -e API_URL=http://stage.another-example.com \
6 -e API_KEY=GBqy23Jr5DZgPsKutvtKsvNQ \
7 -p 4200:4200 \
8 tutorial-env:latest
image 4

As you can see the env variables from docker run are properly forwared into the Angular app. Of course there can be further improvements, you can overwrite development configuration as well, or pass any Angular serve options in the same manner. But I will leave you at that.

I hope this tutorial was helpful, and at least now anyone who encounters similar issues can use this approach as a starting point.

GitHub repo with source code for this tutorial


In case you’d like to check my other work or contact me:

More articles from TekLeo

Omoide Cache introduction, quick and easy caching in Python

Introducing Omoide Cache - a robust, highly tunable and easy-to-integrate in-memory cache solution

July 4th, 2022 · 2 min read

Ping and SYN flood attacks with Python and Scapy

Continuing our exploration of DDoS attacks - this time SYN and ping flood

March 14th, 2022 · 2 min read
© 2020–2022 TekLeo
Link to $https://tekleo.net/Link to $https://github.com/jpleorxLink to $https://medium.com/@leo.ertunaLink to $https://www.linkedin.com/in/leo-ertuna-14b539187/Link to $mailto:leo.ertuna@gmail.com