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";34@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.apiKey13}
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>78 <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>1819 <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:

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

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/sh23# Check the variables exist4echo $API_URL5echo $API_KEY67# Check initial config8cat /app/src/environments/environment.prod.ts910# Replace config11cat >/app/src/environments/environment.prod.ts <<EOL12export const environment = {13 production: true,14 apiUrl: "${API_URL}",15 apiKey: "${API_KEY}",16};17EOL1819# Check resuling config20cat /app/src/environments/environment.prod.ts2122# Run the app23ng 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 node2FROM node:latest as node34# Install angular cli in container5RUN npm install -g @angular/cli67# Copy our app's source code8COPY . /app9WORKDIR /app1011# Build our Angular app12RUN npm install13RUN ng build --prod1415# Mark shell script as executable16RUN chmod a+x main.sh1718# Launch the script19CMD './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

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

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: