DEV Community

Yvan NGOUDJOU
Yvan NGOUDJOU

Posted on

Construire une seule image Docker pour une application Angular et l’exécuter sur tous les environnements

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  
Enter fullscreen mode Exit fullscreen mode

Lors du build, Angular permet de choisir l’environnement à utiliser grâce au flag :

ng build --configuration=staging
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Exemple de contenu :

{
  "apiUrl": "http://localhost:8080/api",
  "featureFlag": true
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Initialiser la configuration avant le bootstrap

Grâce à provideAppInitializer :

bootstrapApplication(AppComponent, {
  providers: [
    provideAppInitializer(async () => {
      const configService = inject(ConfigService);
      await configService.load();
    })
  ]
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)