Dynamically set Angular Environment Variables in Docker
Angular's environment variables are baked into the application on every build and not meant to be changed afterwards by default. This requires a separate build for every environment. In the world of containers it is common, to configure the app via environment variables. Here is how to achieve that.
Angular's environment variables are baked into the application on every build and not meant to be changed afterwards by default. This requires a separate build for every environment. In the world of containers it is common, to configure the app via environment variables. Here is how to achieve that.
According to the Twelve-Factor App, configuration should be stored in the environment. By default, this is not possible with Angular's built-in environment variables. So we need to feed them from an external source.
Angular Environment from an external source
The default environment.ts
is getting cross-compiled into a nearly un-editable JavaScript file on every build and is not meant to change afterwards. One way around that, can be externalizing the configuration from the cross-compiles app bundle.
In the /assets
folder, create a new file called env.js
with the following content:
(function(window) {
window["env"] = window["env"] || {};
// Environment variables
window["env"]["apiUrl"] = "https://api.myapp.com";
window["env"]["debug"] = true;
})(this);
This JavaScript function defines our future environment variables. As is is part of the /assets
folder, it won't be cross-compiles but simply copied to the /dist
directory and can be edited in clear text later.
Call the function at application startup by adding it to the index.html
file:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
<!-- Load environment variables -->
<script src="assets/env.js"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
Now open Angular's environemnt.ts
file and feed it's values with the ones from the env.js
file:
export const environment = {
production: false,
apiUrl: window["env"]["apiUrl"] || "default",
debug: window["env"]["debug"] || false
};
What we achieved by now is that the environments.ts
values are coming from an external source. In our case, this source is env.js
.
Create an environment variable template
Now that Angular's environment variables are provided by the external env.js
file, we need a way to dynamically set the values in this file. For that, we create a new env.template.js
file next to the env.js
file in the /assets
folder. The content is an exact copy of env.js
, just with placeholder variables.
(function(window) {
window.env = window.env || {};
// Environment variables
window["env"]["apiUrl"] = "${API_URL}";
window["env"]["debug"] = "${DEBUG}";
})(this);
These ${PLACEHOLDER}
variables can now be overwritten in your CI/CD pipeline or Docker image with the envsubst
command. This command can create a new env.js
file based one the template and replace the placeholders with environment variables.
# Set environment variable
export API_URL="https://new-api.myapp.com";
# Replace variables in env.js
envsubst < assets/env.template.js > assets/env.js
Replace placeholders inside a Dockerfile
Of course, we can use the same envsubst
command when booting up a Docker container. The last line of the following sample Dockerfile
shows how to replace the env.js
placeholder with values from actual environment variables on every startup.
#################
# Build the app #
#################
FROM node:12-alpine as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm install -g @angular/cli
RUN ng build --configuration production --output-path=/dist
################
# Run in NGINX #
################
FROM nginx:alpine
COPY --from=build /dist /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;'"]
A configuration variable like API_URL
can now be set using an environment variable with the container.
docker run --env API_URL="https://demo-api.myapp.com" my-container:latest
☝️ Advertisement Block: I will buy myself a pizza every time I make enough money with these ads to do so. So please feed a hungry developer and consider disabling your Ad Blocker.