Docker et TypeScript : conteneurisation avancée d'applications
\n\nLa conteneurisation d'applications TypeScript avec Docker ne se limite pas à empaqueter du code dans une image. À un niveau avancé, c'est un art d'optimisation, de sécurité et d'automatisation. Dans cet article, nous verrons comment créer des images Docker efficaces pour TypeScript, gérer l'environnement et intégrer la conteneurisation dans un pipeline CI/CD. Vous apprendrez à réduire la taille de l'image de 5 à 10 fois, accélérer la construction et améliorer l'isolation des dépendances.
\n\n1. Builds multi-étapes (Multi-stage builds) pour TypeScript
\n\nLe problème classique des images Docker pour TypeScript est l'inclusion dans l'image finale de tous les outils de développement : compilateur TypeScript, dépendances de développement, fichiers sources. Cela conduit à des tailles énormes (souvent > 1 Go). La solution est le build multi-étapes.
\n\nL'idée est simple : la première étape (build) compile le TypeScript en JavaScript et installe toutes les dépendances. La deuxième étape (production) copie uniquement le JavaScript prêt et les dépendances de production.
\n\n# Étape 1 : Build\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=development\nCOPY . .\nRUN npm run build\n\n# Étape 2 : Production\nFROM node:20-alpine AS production\nWORKDIR /app\nCOPY --from=builder /app/dist ./dist\nCOPY --from=builder /app/package*.json ./\nRUN npm ci --only=production\nEXPOSE 3000\nCMD ["node", "dist/index.js"]\n\nCette approche réduit la taille de l'image d'environ 800 Mo à environ 150 Mo. Notez les options --only=development et --only=production — ce sont des drapeaux clés pour séparer les dépendances.
2. Optimisation de la mise en cache des couches Docker
\n\nChaque instruction dans un Dockerfile crée une couche. Docker met en cache les couches si elles n'ont pas changé. L'ordre correct des instructions est crucial pour la vitesse des reconstructions.
\n\nMauvais exemple (casse le cache) :
\nFROM node:20-alpine\nWORKDIR /app\nCOPY . .\nRUN npm ci\nRUN npm run build\nCMD ["node", "dist/index.js"]\n\nIci, toute modification du code source (même dans README.md) réinitialise le cache pour npm ci et le build.
\n\nBon exemple (conserve le cache) :
\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json tsconfig.json ./\nRUN npm ci --only=development\nCOPY src ./src\nRUN npm run build\n\nPourquoi cela fonctionne-t-il ? Les dépendances changent rarement, tandis que le code source change souvent. En copiant package*.json séparément, nous garantissons que la couche avec npm ci n'est reconstruite que lorsque ces fichiers changent.
3. Gestion des variables d'environnement et des secrets
\n\nPour les applications TypeScript sur Node.js, l'utilisation de variables d'environnement est la norme. Cependant, Docker présente des particularités.
\n\nErreur typique : transmettre des données sensibles (clés API, mots de passe) via ENV dans le Dockerfile. Cela laisse les secrets dans l'historique des couches de l'image.
Approche correcte :
\n- \n
- Utilisez
--env-filelors du lancement du conteneur \n - Pour la production, utilisez Docker Secrets (dans Swarm) ou Kubernetes Secrets \n
- Pour le développement local, utilisez un fichier
.envavecdocker-compose\n
Exemple de docker-compose.yml :
\nversion: '3.8'\nservices:\n app:\n build: .\n ports:\n - "3000:3000"\n env_file:\n - .env.production\n volumes:\n - ./logs:/app/logs\n\nDans le code TypeScript, utilisez la bibliothèque dotenv pour charger les variables :
import dotenv from 'dotenv';\nimport { resolve } from 'path';\n\ndotenv.config({ path: resolve(__dirname, '../.env') });\n\nconst config = {\n port: parseInt(process.env.PORT || '3000', 10),\n dbUrl: process.env.DATABASE_URL,\n apiKey: process.env.API_KEY\n};\n\nexport default config;\n\nImportant : ne committez jamais les fichiers .env réels