Docker und TypeScript: Fortgeschrittene Containerisierung von Anwendungen
\n\nDie Containerisierung von TypeScript-Anwendungen mit Docker ist nicht einfach nur das Verpacken von Code in ein Image. Auf fortgeschrittenem Niveau ist es eine Kunst der Optimierung, Sicherheit und Automatisierung. In diesem Artikel werden wir uns damit befassen, wie man effiziente Docker-Images für TypeScript erstellt, die Umgebung verwaltet und die Containerisierung in eine CI/CD-Pipeline integriert. Sie erfahren, wie Sie die Image-Größe um das 5- bis 10-fache reduzieren, den Build beschleunigen und die Isolation von Abhängigkeiten verbessern.
\n\n1. Multi-Stage-Builds für TypeScript
\n\nEin klassisches Problem von Docker-Images für TypeScript ist die Aufnahme aller Entwicklungswerkzeuge in das finale Image: TypeScript-Compiler, dev-Abhängigkeiten, Quelldateien. Dies führt zu enormen Größen (oft > 1 GB). Die Lösung sind Multi-Stage-Builds.
\n\nDie Idee ist einfach: Die erste Stufe (build) kompiliert TypeScript in JavaScript und installiert alle Abhängigkeiten. Die zweite Stufe (production) kopiert nur das fertige JavaScript und die production-Abhängigkeiten.
\n\n# Stufe 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# Stufe 2: Produktion\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\nDieser Ansatz reduziert die Image-Größe von ~800 MB auf ~150 MB. Beachten Sie --only=development und --only=production – dies sind die entscheidenden Flags zur Trennung der Abhängigkeiten.
2. Optimierung des Docker-Layer-Cachings
\n\nJede Anweisung in einem Dockerfile erzeugt einen Layer. Docker cached Layer, wenn sie sich nicht geändert haben. Die richtige Reihenfolge der Anweisungen ist entscheidend für die Geschwindigkeit wiederholter Builds.
\n\nSchlechtes Beispiel (bricht den Cache):
\nFROM node:20-alpine\nWORKDIR /app\nCOPY . .\nRUN npm ci\nRUN npm run build\nCMD ["node", "dist/index.js"]\n\nHier setzt jede Änderung im Quellcode (selbst in der README.md) den Cache für npm ci und den Build zurück.
\n\nGutes Beispiel (erhält den 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\nWarum funktioniert das? Abhängigkeiten ändern sich selten, der Quellcode jedoch häufig. Indem wir package*.json separat kopieren, stellen wir sicher, dass der Layer mit npm ci nur neu erstellt wird, wenn sich diese Dateien ändern.
3. Arbeiten mit Umgebungsvariablen und Secrets
\n\nFür TypeScript-Anwendungen auf Node.js ist die Verwendung von Umgebungsvariablen Standard. Bei Docker gibt es jedoch Nuancen.
\n\nTypischer Fehler: Übergabe sensibler Daten (API-Schlüssel, Passwörter) über ENV im Dockerfile. Dies hinterlässt Secrets in der Layer-Historie des Images.
Richtiger Ansatz:
\n- \n
- Verwenden Sie
--env-filebeim Starten des Containers \n - Für die Produktion verwenden Sie Docker Secrets (in Swarm) oder Kubernetes Secrets \n
- Für die lokale Entwicklung – eine
.env-Datei mitdocker-compose\n
Beispiel 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\nIm TypeScript-Code verwenden Sie die Bibliothek dotenv zum Laden der Variablen:
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\nWichtig: Committen Sie niemals echte .env-Dateien