TP09 : Docker propre, sécurisé et optimisé
Objectif du TP
Transformer une application Node.js "mal" dockerisée en une image prête pour la production :
- Configurable : Externalisation de la configuration.
- Sécurisée : Réduction de la surface d'attaque et gestion des droits.
- Optimisée : Réduction de la taille de l'image et gestion des ressources.
Rendu attendu
- Les notes dans votre carnet répondant aux questions posées avec le docker-compose final.
1. Pour débuter
A partir du code suivant qui est celui d'une petite API Express. Créez un dossier tp9 et placez-y les fichiers suivants :
app.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const secret = process.env.APP_SECRET || "mon_super_secret";
app.get('/', (req, res) => {
res.json({
message: "Hello World",
secret: secret
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
package.json
{
"name": "tp9-optimized-node",
"version": "1.0.0",
"main": "app.js",
"dependencies": {
"express": "^4.18.2"
}
}
Dockerfile
docker-compose.yaml
En observant ces fichiers, listez au moins 3 problèmes majeurs (sécurité, taille, configuration) qui empêchent cette application d'être mise en production "proprement".
2. Configuration & Environnement
Objectif
Ne plus jamais coder de secrets ou de configurations (port) en dur.
- Supprimer les valeurs sensibles codées en dur dans
app.js. - Créer un fichier
.envà la racine : - Modifier le
docker-compose.yamlpour injecter ces variables :
Pourquoi ne doit-on jamais versionner le fichier
.envsur Git ? Quelle est la différence entre une variable passée au "Build-time" (ARG) et au "Runtime" (ENV) ?
- Modifiez
app.jspour que leAPP_SECRETne soit plus retourné dans la réponse JSON, mais utilisé uniquement en interne (log de démarrage par exemple).
3. Sécurité
Objectif
Réduire la surface d'attaque et ne pas donner les droits "root" au conteneur.
-
Scanner l'image :
Observez le nombre de vulnérabilités (CVE). -
Corriger le Dockerfile :
- Utiliser une image de base officielle et minimale (
node:20-alpineounode:20-slim). - Utilisateurs non-root : Les images Node officielles incluent un utilisateur
node. Utilisez-le ;) - Limiter les copies : Ne copiez que le nécessaire (et pas des .env ou .git).
- Utiliser une image de base officielle et minimale (
# Exemple de structure sécurisée
FROM node:20-alpine
# Création du répertoire et gestion des droits
WORKDIR /app
RUN chown node:node /app
# Switch vers l'utilisateur non-privilégié
USER node
# Copie des fichiers de dépendances uniquement pour le cache
COPY --chown=node:node package*.json ./
RUN npm install --only=production
# Copie du reste du code
COPY --chown=node:node . .
CMD ["node", "app.js"]
Pourquoi est-il dangereux de laisser un conteneur tourner en tant que
root? Quel est l'impact du passage denode:latestànode:alpinesur le nombre de vulnérabilités détectées par Trivy ?
4. Optimisation & FinOps
Objectif
Réduire la taille de l'image et limiter la consommation de ressources.
-
Utiliser un
.dockerignore: Empêchez l'envoi denode_modules,.env,.gitou des logs vers Docker lors du build. -
Multi-stage Build : Séparez l'étape d'installation des dépendances de l'étape de run pour ne garder que le strict nécessaire.
-
Limitation des ressources dans
docker-compose.yaml:
Comparez la taille de l'image avant (
node:latest) et après (node:alpine+.dockerignore). Quel est le gain en Mo ? Pourquoi la taille de l'image a-t-elle un impact direct sur le coût (FinOps) et la rapidité de déploiement (CI/CD) ?
4.4. FinOps : mesurer avant d'optimiser
On ne peut pas optimiser ce qu'on ne mesure pas. Ces commandes permettent de quantifier l'impact de vos choix.
# Taille de toutes les images locales (triées)
docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# Espace total occupé par Docker sur le disque
docker system df
# Consommation CPU/RAM en temps réel de tous les conteneurs
docker stats
# Processus dans chaque service de la stack
docker compose top
Exercice :
- Lancez votre stack et observez
docker statspendant 30 secondes. - Comparez
docker image lsavant et après avoir ajouté.dockerignoreet le multi-stage build. - Lancez
docker system dfet identifiez ce qui consomme le plus d'espace (images, volumes, conteneurs stoppés). - Utilisez
docker system prunepour nettoyer ce qui est inutilisé, puis relancezdocker system df.
| Commande | Ce qu'elle mesure | Utilité FinOps |
|---|---|---|
docker image ls |
Taille des images | Coût de stockage registry |
docker system df |
Espace total Docker | Audit disque |
docker stats |
CPU/RAM en temps réel | Right-sizing des limites |
docker compose top |
Processus par service | Détecter les fuites de processus |
Question : Si vous limitez un service à
memory: 256Met qu'il consomme régulièrement 240M, que faut-il faire ? Et s'il consomme seulement 50M ?
5. Conclusion
À la fin de ce TP, vous devez être capable d'expliquer :
- Pourquoi l'ordre des instructions COPY et RUN impacte le temps de build.
- Comment une image plus légère améliore la sécurité.
- Comment orchestrer proprement les secrets entre Docker et votre application.