HomeContact

Dynamically Injecting Environment Variables in a Dockerized Angular Application

By Shady Nagy
Published in Angular
May 25, 2023
3 min read
Dynamically Injecting Environment Variables in a Dockerized Angular Application

Table Of Contents

01
Introduction
02
The Problem
03
The Solution
04
Step 1: Create an Entrypoint Script
05
Step 2: Modify Dockerfile
06
Step 3: Create ConfigService
07
Step 4: Load Configurations at Application Startup
08
Conclusion
09
Further Reading
10
Troubleshooting
11
Feedback and Questions

Introduction

In this blog post, we’ll address a common issue developers face when Dockerizing Angular applications - injecting environment variables at runtime.

The Problem

When Dockerizing an application, we often build a static configuration during the image build phase. However, this can cause problems when we want to use dynamic values, such as environment variables that are only available at container runtime.

Consider the following scenario:

You have an Angular application that you want to serve using Nginx, and you want to Dockerize this setup. Your Angular application needs a BASE_URL that should be provided as an environment variable. The challenge is that the environment variable isn’t known until the container is run, and yet you’re trying to use this environment variable during the image build phase.

The Solution

The solution to this issue is to generate the config.json file when the container starts. This allows us to utilize the environment variables passed to the container at runtime.

Step 1: Create an Entrypoint Script

First, create a shell script (entrypoint.sh) that generates the config.json file and starts Nginx.

#!/bin/sh
# Write environment variable to /usr/share/nginx/html/config.json
echo "{\"BASE_URL\": \"$BASE_URL\"}" > /usr/share/nginx/html/config.json
# Start nginx
nginx -g 'daemon off;'

This script first creates a config.json file with the BASE_URL obtained from the environment variable and then starts the Nginx server.

Step 2: Modify Dockerfile

Next, modify the Dockerfile to copy this script into the image and make it the entrypoint.

# Stage 1: Build the Angular app
FROM node:18 AS build
WORKDIR /app
COPY frontend/package.json ../../frontend/package-lock.json ./
RUN npm ci
COPY frontend/. .
RUN npm run build -- --configuration production
# Stage 2: Serve the Angular app with Nginx
FROM nginx:1.23.4-alpine
COPY --from=build /app/dist/frontend /usr/share/nginx/html
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
CMD ["/entrypoint.sh"]
#ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]

In the Dockerfile, the entrypoint.sh script is copied into the image and given executable permissions. The ENTRYPOINT/CMD command is used to execute the script when a container is launched from the image.

Step 3: Create ConfigService

In our Angular application, we need a way to load the config.json file generated at runtime. For this, we create a ConfigService.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root',
})
export class ConfigService {
constructor(private http: HttpClient) { }
load(): Promise<any> {
return this.http.get('/config.json')
.toPromise()
.then(config => {
Object.assign(environment, config);
});
}
}

This ConfigService uses Angular’s HttpClient to perform a GET request to /config.json. It then merges the response into the environment object, effectively loading the runtime configuration into the application.

Step 4: Load Configurations at Application Startup

Finally, to ensure that the configuration is loaded when the application starts, you should call the load method from ConfigService in the APP_INITIALIZER provider in your AppModule.

The APP_INITIALIZER is a special provider token that Angular uses to run specific functions when the application initializes.

Here’s how to add this configuration in your AppModule:

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { ConfigService } from './config.service';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (configService: ConfigService) => () => configService.load(),
deps: [ConfigService],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }

In this configuration, APP_INITIALIZER is provided with a factory function that returns the load method from ConfigService. This method is executed when the application initializes, and it fetches the configuration from the config.json file and assigns it to the environment object.

Conclusion

The approach described in this blog post allows you to inject environment variables into your Dockerized Angular application at runtime, meaning you can run the same Docker image in different environments. This not only saves time and resources that would otherwise be spent building images for each environment, but it also simplifies the deployment process. This strategy is very useful when you need to Dockerize applications that require runtime configuration, and it highlights the flexibility and power that Docker offers when combined with Angular.

Further Reading

For more information and resources about Angular and managing multiple environments, you can refer to the following links:

  1. Angular Official Documentation: Explore the extensive documentation on Angular’s features, best practices, and more.
  2. TypeScript Official Documentation: Learn more about TypeScript, its features, and usage.
  3. Angular Environment Configuration: Dive deeper into Angular environment configuration and best practices.

Troubleshooting

If you encounter any issues while following this tutorial, consider checking the following common problems and their solutions:

  1. Angular build fails with unknown configuration: Make sure you have correctly defined your new environment configuration in the angular.json file and double-check the environment file’s path.
  2. Environment variables not working as expected: Verify that you are importing the correct environment file in your application and that you have replaced the default environment import statement with the new one.
  3. The entrypoint script is not working as expected: Please ensure that the shebang in your entrypoint script is compatible with the shell environment of your Docker container. If #!/bin/sh doesn’t work, you may use #!/bin/bash if it’s available in your Docker image.

Feedback and Questions

We’d love to hear your feedback on this tutorial! If you have any questions or suggestions for improvement, please don’t hesitate to reach out. You can leave a comment below, or you can contact us through the following channels:

  1. Email: shady@shadynagy.com
  2. Twitter: @ShadyNagy_
  3. LinkedIn: Shady Nagy

We’ll do our best to address any questions or concerns you may have. We look forward to hearing from you and helping you make the most of Angular and managing multiple environments in your applications!


Tags

#Angular#Typescript#Javascript#Multi Environment#Environment#Docker

Share


Previous Article
Managing Authentication Tokens in .NET Core with HttpClientFactory and DelegatingHandlers
Shady Nagy

Shady Nagy

Software Innovation Architect

Topics

AI
Angular
dotnet
GatsbyJS
Github
Linux
MS SQL
Oracle

Related Posts

Managing Multiple Angular Projects with Different Versions on a Single Machine
Managing Multiple Angular Projects with Different Versions on a Single Machine
February 05, 2024
2 min

Quick Links

Contact Us

Social Media