Accessing Runtime Environment Variables in a Dockerized Angular Application
This article presents a solution for dynamically setting environment variables at runtime in a Dockerized Angular application.
When deploying Angular applications across different environments, it's crucial to manage configuration settings such as API endpoints or secret keys without hardcoding them into the source code. This article presents a solution for dynamically setting environment variables at runtime in a Dockerized Angular application.
The Problem
In Angular, environment configuration is typically handled through environment.ts
and environment.prod.ts
files in the src/environments
folder. Here's a typical setup:
// src/environments/environment.ts
export const environment = {
production: false,
API_KEY: '1234_API_KEY_5678',
ANOTHER_API_SECRET: '__ANOTHER_SECRET__'
};
These files are integral to the Angular build process, but if sensitive data like API keys are stored here, they become exposed once the repository is shared or public. We need a method to inject these variables at runtime, especially when deploying the same application across various environments with different configurations.
The Solution: Runtime Variables with Docker
Dockerizing the Angular Application
First, let's containerize the Angular application using Docker. Here's a sample
# Start with a Node.js 18 Alpine image.
FROM node:18-alpine as build
# Set the working directory inside the container to /app.
# This is where the app's files will be placed inside the container.
WORKDIR /app
# Install Angular CLI globally inside the container.
# This is necessary for building the Angular application.
RUN npm install -g @angular/cli@14.2.10
# Copy the package.json and package-lock.json files from the project directory
# to the /app directory in the container.
# These files define the project dependencies.
COPY ./package.json ./
COPY ./package-lock.json ./
# Install all dependencies defined in package.json.
# The --force flag ensures that the installation proceeds even if there are conflicts.
RUN npm install --force
# Copy all the project files into the /app directory in the container.
# This includes all source code, Angular configuration files, etc.
COPY . .
# Build the Angular application in production mode.
# The output will be stored in the /app/dist directory inside the container.
RUN ng build --configuration=production --output-path=dist
# Copy the server.js file (Node.js server script) to the container.
# This script is responsible for serving the Angular application at runtime.
COPY server.js ./
# Expose port 4200. This is the port that the Node.js server will listen on.
EXPOSE 4200
# The command to start the Node.js server using server.js.
# When the container starts, it will execute this command.
CMD ["node", "server.js"]
In the Dockerfile setup for your Angular application, we start by using `node:18-alpine` as the base image, selected for its small size and efficiency, ideal for containerized environments. The setup process begins with setting `/app` as the working directory in the Docker container, ensuring that all operations take place in this designated area.
The Dockerfile then proceeds to install Angular CLI globally within the container. This step is crucial for building the Angular application later in the process. Following this, `package.json` and `package-lock.json` files are copied into the container to define project dependencies, which are subsequently installed using `npm install --force`. This ensures all necessary dependencies are available in the container environment.
Next, all project files, including source code and Angular configurations, are copied into the container. With all the necessary files in place, the Angular application is built in production mode, with the build artifacts stored in the `dist` directory.
The `server.js` file is then added to the container. This script is key to serving the Angular application and handling runtime environment variables. To make the application accessible, port 4200 is exposed, which is where the Node.js server will listen for incoming requests. Finally, the Dockerfile specifies the command to start the Node.js server, effectively serving the Angular application within the container.
This Dockerfile thus encapsulates a complete setup for running a containerized Angular application, ensuring a consistent and isolated environment for deployment.
Creating a Custom Server with Express.js
The server.js
file serves the static Angular files and provides an endpoint to serve a config.js
file, which will contain our runtime environment variables.
const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 4200;
const angularAppPath = path.join(__dirname, 'dist');
app.use(express.static(angularAppPath));
app.get('/config.js', (req, res) => {
res.type('.js');
const configData = `
window._env_ = {
BACKEND_URL: "${process.env.BACKEND_API_URL || ''}",
};
`;
res.send(configData);
});
app.get('*', (req, res) => {
res.sendFile(path.join(angularAppPath, 'index.html'));
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Modifying Angular to Use Runtime Variables
In your Angular index.html
, include the config.js
script:
Pay attention to the line that has
Access these variables in your Angular service or component like so:
import { Injectable } from '@angular/core';
// Ensure that TypeScript is aware of the augmented Window interface
declare global {
interface Window {
_env_: {
BACKEND_URL: string;
DEMO_TOKEN: string;
};
}
}
@Injectable({
providedIn: 'root',
})
export class DataService {
private backendUrl: string;
private demoToken: string;
constructor() {
// Accessing the environment variables from window._env_
this.backendUrl = window._env_.BACKEND_URL;
this.demoToken = window._env_.DEMO_TOKEN;
}
// Example method that uses the environment variables
fetchData() {
const url = `${this.backendUrl}/data`;
// Use the url and token as needed
}
}
Testing:
Build the Docker Image:
docker build -t angular-app .
Run the Docker Container:
docker run -p 4200:4200 -e BACKEND_API_URL=http://localhost:2000 angular-app
- The
-p 4200:4200
option maps port 4200 from the container to port 4200 on your host machine, allowing you to access the app viahttp://localhost:4200
. - The
-e
flags set the environment variablesBACKEND_API_URL
in the container
Access the Application:
Open http://localhost:4200
in your web browser.
What's Your Reaction?