Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

configure Angular 2 Webpack App in Docker container environment specific

We want to deploy our Angular 2 app using Docker images in different environments (staging/test, production, ...)

When developing locally we are connecting to the backend REST API via http://localhost:8080 but when we deploy in the different environments we want to use the same Docker image and connect to a different REST API endpoint.

What would be the preferred way to inject this configuration into the Docker container at runtime?

Is there a way to do this via environment variables?

Can we do this via a plain text file containing something like

{
    "BASE_URL": "https://api.test.example.com"
}
like image 436
Thomas Einwaller Avatar asked Nov 02 '16 12:11

Thomas Einwaller


3 Answers

I would have taken a slightly different, but in a way similar approach to your sed-shellscript.

I like the idea of the "plain text config file" that you mentioned, how about this:

Serve the config file from a known location, e.g. ./config? Since your JS is already loaded from the current site, at least there you should be able to rely on a relative path.

Step 1: The shellscript starting up in the docker container writes the config parameters from the environment variables to the plaintext file and places it in the same folder as the packed angular code.

Step 2: In the main HTML file that loads the app there is some init code like this:

$(function() {
  $.get('./config')
    .then(function(res) {
      window.appConfig = res.data;
      // your bootstrapping call
      angular.bootstrap(....);
    });
});

Step 3: You use the global variables in your angular app, e.g. if you are using OpaqueToken:

import { OpaqueToken } from '@angular/core';

const CONFIG_TOKEN = new OpaqueToken('config');

export const THIRDPARTYLIBPROVIDERS = [
  { provide: CONFIG_TOKEN, useValue: window.appConfig }
];

Yes this is still a bit hacky & in your dev-env the node-proxy that you use for serving the ng-app also has to expose such a config endpoint. Also this needs an additional request, but this could be easily avoided by extending the init-code a bit to cache the data in localStorage for example (given that this data won't really change over time).

But all in all I think this is a bit more maintainable than sed-ing some files of which you don't really know how they are layed out.

Let me know what you think!

like image 185
NoUsername Avatar answered Oct 09 '22 16:10

NoUsername


After having some discussions in this post and on twitter it looks like there is no easy way to achieve what I want via Webpack. The files are only served as static files at runtime and it is not possible to exclude a file at build time and include it at runtime.

So I decided to go with the solution/workaround I had in mind: changing the static files when starting up the docker container.

I create my docker image by doing

npm run build:prod
docker build -t angularapp .

I am using the official nginx docker image as my base image and the Dockerfile looks like

FROM nginx:1.11.1

COPY dist /usr/share/nginx/html
COPY run.sh /run.sh

CMD ["bash", "/run.sh"]

The run.sh is used to modify the config file via sed and to start nginx afterwards:

#!/bin/sh

/bin/sed -i "s|http://localhost:8080|${BASE_URL}|" /usr/share/nginx/html/api.config.chunk.js

nginx -g 'daemon off;'

This allows me to configure the BASE_URL via environment variabel in my docker-compose.yml file (simplified):

version: '2'

services:
  api:
    image: restapi
  frontend:
    image: angularapp
    environment:
      BASE_URL: https://api.test.example.com

With this solution/workaround I can deploy the docker image created by my jenkins job for a specific version deploy in all my environments (development, staging, production) by configuring the REST API endpoint used via environment variable when starting the docker container.

like image 10
Thomas Einwaller Avatar answered Oct 09 '22 18:10

Thomas Einwaller


The final solution here is completely dependency on what your CI / CD toolchain looks like but this solution can be molded into pretty much anything.

First step: Add something like https://github.com/motdotla/dotenv to your dependencies this will be handling your config values. There are other optiions & depending on your needs, rolling your own is easy enough.

Per the docs, load the config as early as possible in your app ( global app.module.ts is my personal choice as we want this to be globally available ).

Simply - Based on process.env.NODE_ENV you are going to load different configs per stack and to make the DX simple, I always give config values a default so my developers don't have to bother with the file.

For TESTING, STAGING, PRODUCTION - As an example, you want to set BASE_URL_STAGING & BASE_URL_PRODUCTION in the environment variables for whatever CI provider you are using.

As a part of your CI run & based on git branch, write your config values into a .env file and then add a COPY into your Dockerfile || docker-compose.yml to pull in the environment file you just wrote during your docker build.

After your validations, when you push your new docker image, the .env is part of your deployment package targeting what ever environment specific endpoints you need.

like image 3
Joshua Wiens Avatar answered Oct 09 '22 18:10

Joshua Wiens