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) ?
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.