Build once deploy to multiple environments πŸš€

profile
Tim Deschryver
timdeschryver.dev

The twelve-factor app link

If we take a look at the description of such an app, it says:

In the modern era, software is commonly delivered as a service: called web apps, or software-as-a-service. The twelve-factor app is a methodology for building software-as-a-service apps that:

A twelve-factor app has 12 factors as the name implies, for this post we're scoping into the fifth topic Build, release, run. If we take a look at the description of this stage, it says:

A codebase is transformed into a (non-development) deploy through three stages:

The twelve-factor app uses strict separation between the build, release, and run stages. For example, it is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage.

Note

This blog post was updated in context of standalone components, if you're looking for that see Multiple releases using the same but configurable Angular Standalone Application build

environment.ts link

I believe this file is miss-used in most of the cases, maybe even the most miss-used file in an Angular application. In my opinion, Angular should be more explicit about the use of this file. Sadly, we weren't an exception and we didn't know it until some time ago. Our environment files had some config variables inside of them that varied between environments, e.g. the API endpoint or a token to use a 3rd party service. This caused us to create a separate environment file for each environment dev, test, staging, production1, and production2. By storing our config variables in the environment files it forced us to build the application for each environment when we needed to deploy a new version of our application. This was time-consuming and it was also error-prone. Short said, it is considered a bad practice.

As the start of our quest we extracted all of the environment specific variables into a config file, which is just a simple JSON file. This left us with 2 environment files to define if it's a production build or not. These will be familiar as these are also the default files provided by Angular.

While we develop the application we're using the environment.ts file, when we create a build on our CI server we use the environment.prod.ts file. The Angular CLI picks up the correct file via the angular.json file.

Deploying the application link

Now how should we deploy to an environment with the correct config variables? To do this, we will have to override the config file during the deployment. Depending on the infrastructure and tools that you're using, you could either replace the config file with the correct one, or you could define the config variables during your deploy step and override each config variable with the set variable.

We now have one build and multiple deploys, but we still have to load in the config file in our application. This is the part where we struggled for a bit.

Using the config file link

We thought we weren't the first ones on this quest so we went online in order to solve our problem. All of the solutions we encountered were using the APP_INITIALIZER provider to postpone the bootstrap process of the Angular components until the config file was loaded.

format_quote

For more info and a complete example of an implementation I highly recommend Juri Strumpflohner's blog Compile-time vs. Runtime configuration of your Angular App

At first this seemed like a good solution and after implementing it, it did what it supposed to do and it worked. We were happy.

Until at a specific moment where we needed to have access to our config from outside components and services, we had other configuration objects depending on the config file at the start up. We needed to create a configuration object to load a module, an example is that we needed to load ApplicationInsightsModule at startup. We tried to chain multiple APP_INITIALIZER providers in the hope we didn't have to start over. Unfortunately, we discovered that the config wasn't loaded at the time when we needed it. We tried multiple ways to get around it without changing our solution with the APP_INITIALIZER provider, but without any success.

So we went back to the drawing board and asked for some help in the AngularInDepth group where Joost Koehoorn and Lars Gyrup Brink Nielsen offered some great guidance. With some help we discovered that platformBrowserDynamic also accepts providers.

platformBrowserDynamic link

We needed to have the config variables loaded in before the application booted up, in order to do this we took the bootstrapping process a step further. We had to load the config file first, before everything else, before the application was loaded in. Angular boots up the application module inside the main.ts file. In order to solve our problem we had to postpone this process until we loaded our config variables. Because platformBrowserDynamic accepts providers, we saw an opportunity to define the config here.

The code below loads the config file via the fetch API. This is a Promise, so we can know for sure that the config file will be loaded before we bootstrap the application module.

We store the retrieved config in the APP_CONFIG (type-safe) token, which is defined in our application. The token consists of all of the configuration variables needed in the application. See the Angular documentation for more info about the configuration token. In our code base, this looks like this.

Using APP_CONFIG link

Now we're a step further, we have loaded the config before we bootstrapped the application but we still have to make use of it. As the last hurdle in our quest, we had to create the ApplicationInsightsModule's config. Because we know the config file is loaded when the AppModule gets loaded, we can now access the config file and provide the Application Insights config. It's also fine to re-use the APP_CONFIG config, but to make the config more scoped and maintainable for the modules, we're chopping up the large config into smaller chunks.

Wrapping up link

We now are fully compliant to the fifth factor of a twelve-factor app, being Build, release, run. This means we can now build our application one time and deploy it to every environment we have. During the deployment to an environment we have to deploy the correct version of the configuration file.

As bonus on having this kind of setup makes it very easy to create and use feature toggles.

TLDR:

Incoming links

Outgoing links

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post on

Twitter LinkedIn