Skip to content

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

FROM node:latest

WORKDIR /app

COPY . .

RUN npm install

CMD ["node", "app.js"]

docker-compose.yaml

services:
  app:
    build: .
    ports:
      - "3000:3000"

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.

  1. Supprimer les valeurs sensibles codées en dur dans app.js.
  2. Créer un fichier .env à la racine :
    PORT=3000
    APP_SECRET=MaSuperCleSecrete
    
  3. Modifier le docker-compose.yaml pour injecter ces variables :
    services:
      app:
        build: .
        ports:
          - "${PORT}:${PORT}"
        env_file:
          - .env
    

Pourquoi ne doit-on jamais versionner le fichier .env sur Git ? Quelle est la différence entre une variable passée au "Build-time" (ARG) et au "Runtime" (ENV) ?

  1. Modifiez app.js pour que le APP_SECRET ne 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.

  1. Scanner l'image :

    docker build -t node-bad .
    trivy image node-bad
    
    Observez le nombre de vulnérabilités (CVE).

  2. Corriger le Dockerfile :

    • Utiliser une image de base officielle et minimale (node:20-alpine ou node: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).
# 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 de node:latest à node:alpine sur 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.

  1. Utiliser un .dockerignore : Empêchez l'envoi de node_modules, .env, .git ou des logs vers Docker lors du build.

    node_modules
    .env
    .git
    npm-debug.log
    

  2. 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.

  3. Limitation des ressources dans docker-compose.yaml :

    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
    

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.