Node.js Apps richtig verpacken: Schluss mit 1GB großen Docker-Images!
Eines der häufigsten Probleme bei der Bereitstellung von Node.js-Anwendungen ist die schiere Größe der generierten Docker-Images. Viele Basis-Tutorials zeigen einfache FROM node:18 Instruktionen, die oft Container von weit über 1 GB produzieren. Das kostet nicht nur Speicherplatz auf dem Server, sondern verlangsamt auch CI/CD-Pipelines enorm und stellt zudem ein größeres Sicherheitsrisiko dar (durch eine größere Angriffsfläche an installierten Tools).
In diesem ausführlichen Guide zeige ich dir, wie du das Problem mit Multi-Stage Builds und dem node:18-alpine Image löst und die finale Größe auf unter 150 MB drückst. Zudem zeige ich dir anhand einer kleinen App, wie du sie am Ende direkt über Docker ins Web bringst und im Browser aufrufst.
Vorbereitung: Projektordner & App-Basis
Um das Deployment zu veranschaulichen und dir volle Sicherheit zu geben, dass du dir dein System nicht versehentlich mit Dateien zumüllst, erstellen wir zunächst einen völlig leeren, sauberen Projekt-Ordner.
Öffne dein Terminal und lege den Ordner an:
mkdir simple-docker-node && cd simple-docker-node
mkdir src
Ab jetzt arbeiten wir ausschließlich innerhalb dieses sicheren Verzeichnisses. Erstelle nun darin folgende zwei Dateien für die rudimentäre App:
simple-docker-node/package.json:
{
"name": "simple-docker-node",
"version": "1.0.0",
"description": "Mini Docker Node App",
"main": "server.js",
"dependencies": {
"express": "^4.18.2"
}
}
simple-docker-node/src/server.js:
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('<h1>Hallo Welt!</h1><p>Diese Node.js App läuft erfolgreich in einem winzigen, performanten Docker-Container.</p>');
});
app.listen(PORT, () => {
console.log(`Der Server hat gestartet und lauscht intern auf Port ${PORT}`);
});
Mit dieser Basis können wir uns nun den Docker-Dateien widmen.
Vergleich: Welches Basis-Image ist das Beste?
Bevor wir an saubere Docker-Images denken, müssen wir klären, worauf unsere App laufen soll:
| Image Tag | Größe (ca.) | Einsatzzweck | Fazit |
|---|---|---|---|
node:18 |
~1.1 GB | Volles Debian-System mit vielen Tools (git, python, etc.). | Zu groß. Nur nutzen, wenn native C++ Bindings zwingend kompiliert werden müssen und C/C++ Know-How fehlt. |
node:18-slim |
~250 MB | Abgespecktes Debian. | Guter Kompromiss. Bietet oft mehr Out-of-the-Box Kompatibilität als Alpine, aber mit weitaus weniger Bloat. |
node:18-alpine |
~115 MB | Basiert auf Alpine Linux. Extrem klein und sicher. | Der Gewinner für Produktion. Fast alle reinen JS-Backends laufen hierauf perfekt und extrem sicher. |
Unsere Wahl fällt für dieses Tutorial ganz klar auf Alpine Linux.
Das perfekte Multi-Stage Dockerfile
Wir unterteilen den Docker-Build in zwei Phasen:
- Build-Phase: Installiert alle Abhängigkeiten (inklusive
devDependencies) und baut den Code (falls z.B. TypeScript im Einsatz ist). - Production-Phase: Nimmt nur den blanken Code mit und führt die App als Nicht-Root-User aus.
Lege dazu folgende Datei (ebenfalls weiter in unserem Projektordner) an:
simple-docker-node/Dockerfile:
# --- STAGE 1: Build ---
FROM node:18-alpine AS builder
WORKDIR /usr/src/app
# Nur package.json kopieren, um Docker Cache optimal zu nutzen!
COPY package*.json ./
# Alle Dependencies installieren (inkl. devDependencies)
RUN npm ci
# Quellcode kopieren
COPY . .
# Optional: RUN npm run build (falls vorhanden)
# --- STAGE 2: Production ---
FROM node:18-alpine
# Setze Umgebung explizit auf Produktion
ENV NODE_ENV=production
WORKDIR /usr/src/app
# WICHTIG: Aus Sicherheitsgründen niemals als root laufen!
# Das offizielle Node-Image bringt automatisch den User 'node' mit.
RUN chown node:node /usr/src/app
USER node
# Kopiere nur package.json und installiere ausschließlich Production-Abhängigkeiten
COPY --chown=node:node package*.json ./
RUN npm ci --only=production
# Kopiere kompilierten oder reinen Quellcode aus Stage 1
COPY --chown=node:node --from=builder /usr/src/app/src ./src
COPY --chown=node:node --from=builder /usr/src/app/src/server.js ./
EXPOSE 3000
# Start-Command
CMD ["node", "server.js"]
Docker Compose: Der elegante Weg zur Ausführung
Statt sich ewig lange und unübersichtliche docker run Befehle zu merken, verwenden wir grundsätzlich Docker Compose. Es ist professioneller und dokumentiert deine Laufzeitumgebung in Form von Code (Infrastructure-as-Code).
Lege diese finale Datei direkt neben das Dockerfile:
simple-docker-node/docker-compose.yaml:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: meine_node_app_prod
ports:
- "3000:3000"
restart: unless-stopped
environment:
- NODE_ENV=production
1. Anwendung bauen & im Hintergrund starten
Mit Docker Compose kannst du nun das Image bauen und den Container direkt elegant im Hintergrund starten:
docker compose up -d --build
⚠️ 📸 SCREENSHOT ANFRAGE: Hier einen Screenshot vom Terminal einfügen, der zeigt, wie Docker die Images baut und die Container erfolgreich startet.
2. Logs prüfen
Um zu sehen, ob deine Node.js-App erfolgreich gestartet ist und welche Ausgaben sie produziert:
docker compose logs -f
3. Anwendung im Webbrowser aufrufen!
Wenn der Server läuft, hat Docker den internen Container-Port 3000 durch das Port-Binding ("3000:3000") nach außen zu deinem Rechner verbunden.
Öffne einen Webbrowser und navigiere zu:
⚠️ 📸 SCREENSHOT ANFRAGE: Hier einen Screenshot vom Browser-Fenster einfügen, der die erfolgreiche "Hallo Welt!" Antwort der Node.js App zeigt.
Juhu! Du solltest nun das "Hallo Welt!" und die HTML-Message deines selbst gehosteten Node-Servers sehen.
4. Der Beweis: Image-Größe überprüfen
Prüfe jetzt einmal, wie groß das erstellte Image am Ende wirklich geworden ist:
docker images | grep meine_node_app
Du solltest in der Konsole eine Image-Größe von lediglich ~130-150 MB sehen!
Fazit
Mit diesem Setup hast du eine perfekte Balance aus extrem geringer Image-Größe, optimaler Docker-Caching-Architektur und höchster Sicherheit geschaffen. Zudem hast du ein perfekt isoliertes Projekt per Setup aufgesetzt und per Browser aufgerufen. Dein Team und deine zukünftigen Deployments werden es dir danken!
Login