Docker est devenu incontournable pour déployer des applications Angular en production. Cependant, beaucoup de développeurs se contentent d'un Dockerfile basique qui fonctionne localement mais génère des images surdimensionnées, des failles de sécurité et des temps de démarrage inacceptables en cluster. L'enjeu réel est de construire une image légère, reproductible et sécurisée, capable de s'intégrer dans un pipeline CI/CD robuste et un orchestrateur comme Kubernetes. Ce processus exige une compréhension des multi-stage builds, de l'optimisation des couches, du choix de l'image de base et de la gestion des variables d'environnement en production.
Stratégie multi-stage : séparer compilation et runtime
Le multi-stage build est la fondation d'une image Angular optimisée. L'idée est simple : compilez votre app dans une image lourde, puis copiez uniquement les artefacts produits (les fichiers du dist/) dans une image légère de runtime. Voici un exemple concret : la première étape utilise node:20-alpine pour la compilation, installe les dépendances, exécute ng build --configuration production, et génère un bundle prêt pour la production. La deuxième étape utilise nginx:alpine comme image de base, copie les fichiers compilés dans /usr/share/nginx/html, et configure Nginx pour servir l'SPA. Cette approche réduit la taille finale de l'image de 1.2 Go à environ 50-80 Mo, ce qui accélère les pulls dans Kubernetes et diminue l'empreinte mémoire.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build -- --configuration production
FROM nginx:alpine
COPY --from=builder /app/dist/my-app /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Choix de l'image de base et sécurité
Alpine Linux (node:20-alpine, nginx:alpine) est le standard pour les conteneurs légers. Avec environ 5 Mo contre 150+ Mo pour Debian, c'est un choix évident. Cependant, Alpine présente un compromis : ses bibliothèques C sont minimalistes (musl au lieu de glibc), ce qui peut créer des incompatibilités avec certaines dépendances natives. Testez votre build localement en utilisant l'image Alpine avant de la déployer. Pour la sécurité, n'utilisez jamais l'utilisateur root dans votre conteneur en production : créez un utilisateur non-root et assignez-lui les permissions minimales. Nginx s'exécute déjà sous un utilisateur nginx par défaut, mais assurez-vous que le répertoire /usr/share/nginx/html est lisible sans droits d'écriture. Enfin, scannez vos images avec des outils comme Trivy ou Grype pour détecter les vulnérabilités CVE dans les dépendances, et maintenez vos images de base à jour avec les patches de sécurité.
Configuration Nginx pour Angular
Nginx doit être configuré spécifiquement pour servir une SPA Angular. Le piège courant est d'oublier la réécriture des routes : sans elle, chaque URL non-existante retourne un 404, alors qu'Angular devrait gérer le routage côté client. Voici la configuration essentielle : toutes les requêtes vers des fichiers statiques (assets, scripts, styles) sont servies directement, mais les requêtes vers des routes Angular sont redirigées vers index.html. Cela permet à Angular Router de reprendre la main et de naviguer correctement. Ajoutez aussi des en-têtes de cache agressifs pour les assets avec hash (immutable), et des en-têtes de non-cache pour index.html, sinon vos utilisateurs resteraient sur une ancienne version après un déploiement.
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location ~* \.(js|css|png|jpg|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, must-revalidate";
}
}Gestion des variables d'environnement
En production, vous ne pouvez pas bâker les variables d'environnement dans le build Docker. Une approche courante est d'injecter les variables au démarrage du conteneur via un script d'initialisation. Créez un fichier assets/config.json qui sera peuplé dynamiquement, ou utilisez un script d'entrée (entrypoint.sh) qui génère un fichier de configuration avant de lancer Nginx. Avec Kubernetes, vous injectez les variables via ConfigMap ou Secret, puis montez-les dans le conteneur. Cela permet de déployer la même image dans dev, staging et prod avec des configurations différentes, sans reconstruire à chaque fois. Évitez absolument de passer des tokens ou des clés API en tant que variables d'environnement Docker visibles dans l'historique de build : utilisez des secrets managés par votre orchestrateur.
Optimisation de la taille et des couches
Chaque ligne du Dockerfile crée une couche qui augmente la taille de l'image. Minimisez le nombre de couches en regroupant les commandes RUN : RUN npm ci && npm run build au lieu de deux lignes séparées. Utilisez .dockerignore pour exclure les fichiers inutiles (node_modules, .git, README) avant la copie, ce qui accélère le build et réduit la taille de la couche. Nettoyez les caches npm après l'installation en production : RUN npm ci --only=production && npm cache clean --force . Enfin, exploitez le cache Docker durant le développement : copiez package.json avant le code source, afin que les dépendances soient cachées et réutilisées si seul le code change.
Pièges courants et surveillance
Le piège le plus fréquent est de tester l'image localement avec docker run sans simuler les vrais conditions de production (pas de limites de mémoire, pas d'orchestration, pas de secrets injectés). Buildez et testez l'image exactement comme elle sera déployée : limitez la mémoire, injectez les variables via ConfigMap, vérifiez les logs. Un second piège : oublier de builder en mode production ou avec une configuration de build optimisée. Vérifiez que ng build --configuration production active la minification, l'ahead-of-time compilation (AOT) et la suppression du code inutilisé. Enfin, surveillez la taille et les vulnérabilités de votre image : intégrez Trivy dans votre CI/CD pour rejeter automatiquement les images contenant des CVE critiques. Testez aussi les temps de startup et la consommation mémoire avec des outils comme k6 ou Locust en environnement de staging.
Conclusion et prochaines étapes
Dockeriser une app Angular pour la production n'est pas une simple affaire de copier un Dockerfile trouvé sur Stack Overflow. Une image optimisée repose sur le multi-stage, le choix d'une base Alpine sécurisée, une configuration Nginx stricte, et une gestion intelligente des variables d'environnement. Commencez par mettre en place un Dockerfile robuste avec les bonnes pratiques exposées ici, intégrez des scans de sécurité dans votre pipeline CI/CD, et testez en conditions réelles avant la production. Une fois en place, vous disposerez d'une image légère, reproductible et prête pour Kubernetes, capable de supporter des millions de requêtes avec une empreinte mémoire minimaliste. L'investissement initial dans une bonne architecture Docker paie rapidement en stabilité, rapidité de déploiement et confiance en production.