Contexte
Dans le développement d’applications modernes, il est fréquent de disposer de plusieurs environnements :
- Dev pour les développeurs,
- Staging pour la validation métier,
- Production pour les utilisateurs finaux.
Dans chacun de ces environnements, on déploie généralement une instance du backend et du frontend.
Côté Angular, cela se traduit par plusieurs fichiers de configuration :
environment.ts
environment.staging.ts
environment.prod.ts
Lors du build, Angular permet de choisir l’environnement à utiliser grâce au flag :
ng build --configuration=staging
Le problème
Avec cette approche, la configuration est injectée au moment du build de l’image Docker.
Cela pose une contrainte forte en contexte CI/CD :
- Si vous construisez votre image Docker avec l’option
--configuration=staging
, alors même en l’exécutant en Dev, elle utilisera toujours la configuration staging. - Vous êtes donc obligés de reconstruire une image par environnement, ce qui va à l’encontre du principe Build Once, Run Everywhere.
L’approche proposée
Puisqu’Angular ne permet pas encore d’injecter des configurations dynamiquement au runtime, une approche pragmatique consiste à :
1. Centraliser la configuration dans un fichier JSON
Placer la configuration dans un fichier env.json
stocké dans /assets/conf
.
Lors du démarrage du conteneur, un script entrypoint.sh
copie le bon fichier selon l’environnement :
cp env.${ENVIRONMENT}.json env.json
Exemple de contenu :
{
"apiUrl": "http://localhost:8080/api",
"featureFlag": true
}
2. Charger le fichier dynamiquement au lancement de l’application
@Injectable({ providedIn: 'root' })
export class ConfigService {
private config!: any;
load(): Promise<void> {
return fetch('/assets/env.json')
.then(response => response.json())
.then(config => this.config = config);
}
get apiUrl(): string {
return this.config.apiUrl;
}
}
3. Initialiser la configuration avant le bootstrap
Grâce à provideAppInitializer
:
bootstrapApplication(AppComponent, {
providers: [
provideAppInitializer(async () => {
const configService = inject(ConfigService);
await configService.load();
})
]
});
4. Exécution de l'image Docker
Exécute l'image docker en spécifiant la valeur de la variable ENVIRONMENT:
docker run -e ENVIRONMENT=staging IMAGE_ID
Bénéfices
- Une seule image Docker à maintenir et à déployer.
- Déploiements simplifiés dans un pipeline CI/CD.
- Séparation claire entre le code applicatif (figé dans l’image) et la configuration (injectée au runtime).
Conclusion
Cette approche permet de réellement appliquer le principe Build Once, Run Everywhere avec Angular dans un contexte Docker et CI/CD.
Il serait intéressant qu’Angular propose à terme un mécanisme natif d’injection de configuration au runtime.
En attendant, cette méthode est utilisée dans de nombreux projets en production et reste une solution robuste et pragmatique.
Et vous, comment gérez-vous la configuration multi-environnements pour vos applications Angular déployées sous Docker ?
Top comments (0)