Configurar las variables de entorno de Angular dinámicamente en Docker

Uno de los ejemplos que utilizo bastante a menudo es uno que consta de una API en .NET Core y un frontal web en Angular. Dependiendo del entorno, la URL de la API puede variar y es por ello que necesitaba que este valor fuera obtenido de manera dinámica según ejecutara el contenedor en un sitio u otro. En este artículo quería compartir contigo cómo estoy pasando variables de entorno a Angular desde el contenedor de Docker.

Modificar los archivos environment.*.ts

El objetivo principal es que estos archivos, donde deberíamos guardar estas variables de entorno, puedan coger el valor que necesitan en cada momento de algún sitio. En este caso los he modificado de la siguiente manera:

export const environment = {
  production: true,
  // apiUrl: 'http://localhost:30040/api/hero'
  apiUrl: window['env']['ApiUrl'] || 'http://localhost:5010/api/hero'
};

Como ves, utilizo window[‘env’] como el lugar donde almacenar estas variables, seguido del nombre de la variable que quiero recuperar. Para que este uso sea posible, debes añadir en el archivo tsconfig.json la propiedad «suppressImplicitAnyIndexErrors»: true, ya que de lo contrario TypeScript dará error al compilar.

Archivos env.js y env.template.js

La forma en la que vamos a rellenar estos valores es a través de un archivo que he llamado env.js ubicado en la carpeta assets del proyecto. Este deberá ir referenciado en el archivo index.html, para que estas variables de entorno estén disponibles para nuestro proyecto de Angular.

(function (window) {
    window['env'] = window['env'] || {};
    // Environment variables
    window['env']['ApiUrl'] = 'http://localhost:5010/api/hero';
})(this);

Este tiene ya un valor por defecto para la única variable de entorno que tengo, pero podría tener tantas como necesite modificar dinámicamente. Si no ejecutamos el código en un entorno contenerizado hará uso del valor que pongamos aquí.

Por otro lado, es necesario tener un segundo archivo, en mi caso llamado env.template.js, donde vamos a utilizar «placeholders» que posteriormente reemplazaré con los valores que me lleguen de la configuración del contenedor:

(function (window) {
    window['env'] = window['env'] || {};
    // Environment variables
    window['env']['ApiUrl'] = '${API_URL}';
})(this);

Dockerfile

Lo último que necesito es que mi Dockerfile sepa reemplazar lo que le llegue, utilizando la plantilla env.template.js y reemplazando el archivo env.js, que es el que finalmente usará mi aplicación. Este quedaría de la siguiente manera:

FROM node:lts-alpine as build-step
RUN mkdir -p /app
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
RUN npm run build --prod
FROM nginx:stable
COPY --from=build-step /app/dist/angular-tour-of-heroes /usr/share/nginx/html
# When the container starts, replace the env.js with values from environment variables
CMD ["/bin/sh",  "-c",  "envsubst < /usr/share/nginx/html/assets/env.template.js > /usr/share/nginx/html/assets/env.js && exec nginx -g 'daemon off;'"]

Como ves, cuando ejecute un contenedor con la imagen generada de este Dockerfile, antes de arrancar Nginx, hace uso de envsubst que reemplazará los placeholder de la plantilla env.template.js por los valores en las variables de entorno configuradas en el contenedor y guardará el resultado en env.js.

Cómo probarlo

Para comprobar que todo funciona como se espera puedes ejecutar un contenedor de la siguiente manera:

#without env var
docker run -p 9090:80 tour-of-heroes
#with env var
docker run -p 9090:80 -e API_URL=http://domain.com/api/hero tour-of-heroes

O incluso ejecutar el frontal en Angular sin contenerizar para comprobar que sigue funcionando correctamente.

¡Saludos!