Dynamically Injecting Environment Variables in a Dockerized Angular Application

Table Of Contents
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.jsonecho "{\"BASE_URL\": \"$BASE_URL\"}" > /usr/share/nginx/html/config.json# Start nginxnginx -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 appFROM node:18 AS buildWORKDIR /appCOPY frontend/package.json ../../frontend/package-lock.json ./RUN npm ciCOPY frontend/. .RUN npm run build -- --configuration production# Stage 2: Serve the Angular app with NginxFROM nginx:1.23.4-alpineCOPY --from=build /app/dist/frontend /usr/share/nginx/htmlCOPY frontend/nginx.conf /etc/nginx/conf.d/default.confCOPY entrypoint.sh /entrypoint.shRUN chmod +x /entrypoint.shCMD ["/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:
- Angular Official Documentation: Explore the extensive documentation on Angular’s features, best practices, and more.
- TypeScript Official Documentation: Learn more about TypeScript, its features, and usage.
- 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:
- Angular build fails with unknown configuration: Make sure you have correctly defined your new environment configuration in the
angular.jsonfile and double-check the environment file’s path. - 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.
- 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/shdoesn’t work, you may use#!/bin/bashif 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:
- Email: shady@shadynagy.com
- Twitter: @ShadyNagy_
- 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!







