Introduction : du monolithe aux microservices avec Docker et JavaScript
Le développement web moderne en JavaScript (notamment dans l'écosystème Node.js) est impensable sans conteneurisation. Docker a cessé d'être un simple outil pour « empaqueter » une application — c'est le standard de facto pour la livraison, la mise à l'échelle et la gestion des microservices. Si vous maîtrisez déjà les bases de Dockerfile et docker run, il est temps de passer au niveau avancé.
Dans cet article, nous verrons comment créer des images Docker efficaces, sécurisées et légères pour Node.js, configurer des environnements multi-conteneurs avec Docker Compose, organiser les interactions entre services via les réseaux et gérer correctement les données avec les volumes. Ce contenu s'adresse aux développeurs souhaitant construire des architectures prêtes pour la production.
1. Optimisation du Dockerfile pour Node.js : couches, mise en cache et sécurité
Un Dockerfile classique pour Node.js ressemble souvent à ceci : FROM node:18, COPY . /app, RUN npm install. Cependant, cette approche conduit à des images gigantesques (plus de 1 Go) et à des builds longs. L'optimisation avancée repose sur trois principes :
1.1 Build multi-étapes (Multi-stage builds)
Séparez le processus en une étape de build (avec compilateurs et dépendances de développement) et une étape d'exécution (uniquement le runtime). Cela réduit l'image finale de 5 à 10 fois.
# ---- Étape de build ----FROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY . .RUN npm run build
# ---- Étape d'exécution ----FROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/main.js"]1.2 Utilisation de .dockerignore et mise en cache des couches
Docker met en cache les couches. Pour éviter de reconstruire npm install à chaque modification de code, copiez package.json séparément. Ajoutez un .dockerignore :
node_modules.git*.md.envdistDésormais, npm install n'est exécuté qu'en cas de modification des dépendances, et non de l'ensemble du projet.
1.3 Sécurité : utilisateur non-root et image de base minimale
Utilisez node:18-alpine (ou même scratch avec un binaire compilé via pkg). Ne lancez jamais Node.js en tant que root :
RUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserCela empêche l'escalade de privilèges en cas de compromission de l'application.
2. Docker Compose : application multi-conteneurs (Node.js + Redis + Nginx)
Une application réelle est rarement composée d'un seul service. Docker Compose permet, avec une seule commande docker-compose up, de déployer toute l'infrastructure. Voici un exemple avancé : une API Node.js, Redis pour la mise en cache et Nginx comme proxy inverse.
2.1 Structure du docker-compose.yml
version: '3.8'services: app: build: context: ./app dockerfile: Dockerfile.prod ports: - "3000:3000" environment: - REDIS_URL=redis://redis:6379 - NODE_ENV=production depends_on: - redis restart: unless-stopped
redis: image: redis:7-alpine volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3
nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - app
volumes: redis-data:2.2 Paramètres avancés : healthcheck et dépendances
depends_on garantit l'ordre de démarrage, mais n'attend pas que le service soit prêt. Utilisez healthcheck pour Redis et un script d'attente dans l'application :
// Dans index.js de Node.jsconst co