Einleitung: Vom Monolithen zu Microservices mit Docker und JavaScript
Moderne Webentwicklung in JavaScript (insbesondere im Node.js-Ökosystem) ist ohne Containerisierung undenkbar. Docker hat aufgehört, nur ein Werkzeug zum "Verpacken" einer Anwendung zu sein — es ist der De-facto-Standard für die Auslieferung, Skalierung und Verwaltung von Microservices. Wenn Sie bereits die grundlegenden Dockerfile- und docker run-Befehle beherrschen, ist es Zeit, zum fortgeschrittenen Level überzugehen.
In diesem Artikel analysieren wir, wie man effiziente, sichere und schlanke Docker-Images für Node.js erstellt, Multi-Container-Umgebungen mit Docker Compose konfiguriert, die serviceübergreifende Kommunikation über Netzwerke organisiert und Daten über Volumes kompetent verwaltet. Das Material richtet sich an Entwickler, die produktionsreife Architekturen erstellen möchten.
1. Optimierung des Dockerfile für Node.js: Layer, Caching und Sicherheit
Ein klassisches Dockerfile für Node.js sieht oft so aus: FROM node:18, COPY . /app, RUN npm install. Dieser Ansatz führt jedoch zu riesigen Images (über 1 GB) und langen Build-Zeiten. Fortgeschrittene Optimierung basiert auf drei Prinzipien:
1.1 Multi-Stage-Builds
Trennen Sie den Prozess in eine Build-Stufe (mit Compilern und dev-Abhängigkeiten) und eine Laufzeit-Stufe (nur Laufzeit). Dadurch wird das endgültige Image um das 5- bis 10-fache reduziert.
# ---- Build Stage ----FROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY . .RUN npm run build
# ---- Runtime Stage ----FROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/main.js"]1.2 Verwendung von .dockerignore und Layer-Caching
Docker cached Layer. Um ein erneutes Ausführen von npm install bei jeder Codeänderung zu vermeiden, kopieren Sie package.json separat. Fügen Sie eine .dockerignore-Datei hinzu:
node_modules.git*.md.envdistJetzt wird npm install nur ausgeführt, wenn sich Abhängigkeiten ändern, nicht das gesamte Projekt.
1.3 Sicherheit: Nicht-Root-Benutzer und minimales Basis-Image
Verwenden Sie node:18-alpine (oder sogar scratch mit einem kompilierten Binary über pkg). Führen Sie Node.js niemals als root aus:
RUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserDies verhindert eine Rechteausweitung im Falle einer Kompromittierung der Anwendung.
2. Docker Compose: Multi-Container-Anwendung (Node.js + Redis + Nginx)
Eine echte Anwendung besteht selten aus nur einem Dienst. Docker Compose ermöglicht es Ihnen, die gesamte Infrastruktur mit einem einzigen Befehl docker-compose up hochzufahren. Betrachten wir ein fortgeschrittenes Beispiel: Node.js-API, Redis für Caching und Nginx als Reverse-Proxy.
2.1 Struktur der 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 Fortgeschrittene Einstellungen: Healthcheck und Abhängigkeiten
depends_on garantiert die Startreihenfolge, wartet aber nicht auf die Bereitschaft des Dienstes. Verwenden Sie healthcheck für Redis und ein Warteskript in der Anwendung:
// In index.js Node.js
const co