In this blog post, we’ll address a common issue developers face when Dockerizing Angular applications - injecting environment variables at runtime.
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 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.
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.
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.
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.
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.
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.
For more information and resources about Angular and managing multiple environments, you can refer to the following links:
If you encounter any issues while following this tutorial, consider checking the following common problems and their solutions:
angular.json
file and double-check the environment file’s path.#!/bin/sh
doesn’t work, you may use #!/bin/bash
if it’s available in your Docker image.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:
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!
Quick Links
Legal Stuff