I recently achieved this by reading my environment variables from a json file. In my case I wanted to run the same docker image in different environments (Dev, QA, Prod), I believe you want to achieve something similar.
Firstly you need a json file, /src/assets/config.json with the data that you want to change during runtime.
{
"APP_API_URL": "http://localhost:3000/api"
}
Then you will have to create a service for reading your json config file, src/app/core/config/environment.service.ts.
The get function will be used to read a specific property from your config (eg: APP_API_URL).
The ENVIRONMENT injectionToken will be used on your services like ProductService, CategoryService, etc..
import { Injectable, InjectionToken } from '@angular/core';
export interface Environment {
APP_API_URL: string;
}
interface EnvironmentToken {
load(): Promise<Environment>;
get(key: keyof Environment): string | undefined;
}
@Injectable({
providedIn: 'root',
})
export class EnvironmentService {
private config?: Environment;
load(): Promise<Environment> {
return fetch('assets/config.json')
.then(res => res.json())
.then((data: Environment) => (this.config = data));
}
get(key: keyof Environment): string | undefined {
return this.config?.[key];
}
}
export const ENVIRONMENT = new InjectionToken<EnvironmentToken>(
'App Environment Config'
);
Now update app.config.ts to load your config when the app is initialized, src/app/app.config.ts
import { inject, provideAppInitializer } from '@angular/core';
import { ENVIRONMENT, EnvironmentService } from './app/core/config/environment.service';
function initApp() {
const environmentService = inject(ENVIRONMENT);
return environmentService.load();
}
export const appConfig: ApplicationConfig = {
providers: [
provideAppInitializer(initApp),
{
provide: ENVIRONMENT,
useValue: new EnvironmentService()
},
...
]
};
You can read the value from your service: src/app/features/product/services/product.service.ts
import { HttpClient, HttpParams } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ENVIRONMENT } from '../../../core/config/environment.token';
@Injectable({
providedIn: 'root',
})
export class ProductService {
private readonly http = inject(HttpClient);
private readonly env = inject(ENVIRONMENT);
private readonly url = this.env.get('APP_API_URL');
getProducts() {
return this.http.get<any[]>(this.url! + '/product');
}
}
Now you need to create a docker-entrypoint.sh file on the same path as your Dockerffile with the following contents:
#!/bin/sh
ENV_FILE=/usr/share/nginx/html/assets/config.json
echo "{" > $ENV_FILE
# This will loop through all environment variables with a prefix of APP_
printenv | grep '^APP_' | while IFS='=' read -r key value; do
echo " \"${key}\": \"${value}\"," >> $ENV_FILE
done
# This will remove a trailing comma
sed -i '$ s/,$//' $ENV_FILE
echo "}" >> $ENV_FILE
exec "$@"
Lastly, in your Dockerfile you will have this under nginx stage:
FROM nginx:alpine
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /usr/src/app/dist/my-app/browser /usr/share/nginx/html
COPY --from=build /usr/src/app/docker-entrypoint.sh /docker-entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/docker-entrypoint.sh"]
# The Chainguard nginx image already runs nginx by default — no need to specify CMD.
CMD ["nginx", "-g", "daemon off;"]
That's all.
Now you can run your container with different variables. Make sure to prefix them with APP_ for this to work properly.
I hope this helps.
RUN npm ciit will pick up the library. If there's only one deployable, does that simplify your issue?