可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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"
}
回答1:
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.
回答2:
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.
回答3:
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!