Docker für Entwickler: Container, Images und Compose
· 12 Min. Lesezeit
Inhaltsverzeichnis
- Docker verstehen: Warum es wichtig ist
- Kernkonzepte erklärt
- Dockerfile-Anatomie und -Struktur
- Dockerfile Best Practices
- Wichtige Docker-Befehle
- Docker Compose für Multi-Container-Apps
- Entwicklungsworkflow und Tipps
- Debugging und Fehlerbehebung
- Sicherheitsüberlegungen
- Leistungsoptimierung
- Häufig gestellte Fragen
- Verwandte Artikel
Docker verstehen: Warum es wichtig ist
Docker hat grundlegend verändert, wie Entwickler Anwendungen erstellen, ausliefern und ausführen. Vor Docker war das Einrichten von Entwicklungsumgebungen ein Albtraum aus Abhängigkeitskonflikten, Versionsinkompatibilitäten und dem berüchtigten „funktioniert auf meinem Rechner"-Problem.
Docker löst dies, indem es Ihre Anwendung mit allem, was sie zum Laufen braucht – Code, Laufzeitumgebung, Systemwerkzeuge, Bibliotheken und Einstellungen – in eine standardisierte Einheit namens Container verpackt. Dieser Container läuft identisch auf Ihrem Laptop, dem Rechner Ihres Kollegen und auf Produktionsservern.
Die Vorteile sind unmittelbar und greifbar:
- Konsistenz: Entwicklungs-, Staging- und Produktionsumgebungen sind identisch
- Isolation: Jede Anwendung läuft in ihrem eigenen Container ohne Konflikte
- Portabilität: Container laufen überall, wo Docker installiert ist
- Effizienz: Container teilen sich den Host-OS-Kernel und verbrauchen weit weniger Ressourcen als virtuelle Maschinen
- Geschwindigkeit: Container starten in Sekunden, nicht in Minuten
Für Entwickler bedeutet Docker, dass Sie einen kompletten Anwendungs-Stack – Webserver, Datenbank, Cache, Message Queue – mit einem einzigen Befehl hochfahren können. Keine stundenlange PostgreSQL-Installation oder Debugging von Redis-Konfigurationsproblemen mehr.
Kernkonzepte erklärt
Das Verstehen der Kernkonzepte von Docker ist essentiell, bevor man in die praktische Nutzung eintaucht. Diese Bausteine arbeiten zusammen, um das Docker-Ökosystem zu schaffen.
| Konzept | Was es ist | Analogie | Hauptmerkmale |
|---|---|---|---|
| Image | Schreibgeschützte Vorlage mit App + Abhängigkeiten | Eine Klassendefinition in OOP | Unveränderlich, geschichtet, teilbar |
| Container | Laufende Instanz eines Images | Ein Objekt (Instanz einer Klasse) | Isoliert, kurzlebig, zustandslos |
| Dockerfile | Anweisungen zum Erstellen eines Images | Ein Rezept oder Bauplan | Textdatei, versionskontrolliert |
| Volume | Persistenter Speicher außerhalb des Containers | Eine externe Festplatte | Überlebt Container-Löschung |
| Network | Kommunikation zwischen Containern | Ein lokales Netzwerk (LAN) | Isoliert, konfigurierbar, sicher |
| Registry | Speicher für Images (Docker Hub) | npm/PyPI für Container | Öffentlich oder privat, versioniert |
Images vs. Container: Der entscheidende Unterschied
Hier werden viele Anfänger verwirrt. Ein Image ist ein statischer Snapshot – stellen Sie es sich als eingefrorene Vorlage vor. Ein Container ist das, was Sie erhalten, wenn Sie dieses Image ausführen – es ist ein lebender, laufender Prozess.
Sie können unbegrenzt viele Container aus einem einzigen Image erstellen, genau wie Sie mehrere Objekte aus einer Klasse erstellen können. Jeder Container ist von den anderen isoliert, selbst wenn sie alle auf demselben Image basieren.
Profi-Tipp: Images werden in Schichten erstellt. Jede Anweisung in Ihrem Dockerfile erstellt eine neue Schicht. Docker cached diese Schichten, sodass beim Neuerstellen eines Images nur die Schichten neu erstellt werden, die sich geändert haben. Das macht Builds unglaublich schnell.
Volumes: Das Persistenzproblem lösen
Container sind von Natur aus kurzlebig – wenn Sie einen Container löschen, verschwindet alles darin. Das ist großartig für zustandslose Anwendungen, aber problematisch für Datenbanken oder Daten, die Sie behalten müssen.
Volumes lösen dies, indem sie Daten außerhalb des Container-Dateisystems speichern. Die Daten bleiben bestehen, selbst wenn Container gelöscht und neu erstellt werden. Es gibt drei Arten von Mounts:
- Volumes: Von Docker verwaltet, in Dockers Speicherbereich gespeichert (empfohlen)
- Bind Mounts: Verknüpfen ein Host-Verzeichnis direkt in den Container (nützlich für Entwicklung)
- tmpfs Mounts: Nur im Host-Speicher gespeichert, nie auf Festplatte geschrieben (für sensible Daten)
Dockerfile-Anatomie und -Struktur
Ein Dockerfile ist ein Textdokument mit Anweisungen zum Erstellen eines Docker-Images. Jede Anweisung erstellt eine Schicht im Image, und Docker cached diese Schichten für Effizienz.
Hier ist eine Aufschlüsselung der häufigsten Dockerfile-Anweisungen:
| Anweisung | Zweck | Beispiel | Best Practice |
|---|---|---|---|
FROM |
Basis-Image zum Aufbauen | FROM node:22-alpine |
Spezifische Versionen verwenden, alpine bevorzugen |
WORKDIR |
Arbeitsverzeichnis festlegen | WORKDIR /app |
Absolute Pfade verwenden |
COPY |
Dateien vom Host ins Image kopieren | COPY package.json . |
Abhängigkeiten zuerst kopieren für Caching |
RUN |
Befehle während des Builds ausführen | RUN npm install |
Befehle mit && verketten, um Schichten zu reduzieren |
EXPOSE |
Dokumentieren, auf welchem Port die App lauscht | EXPOSE 3000 |
Nur Dokumentation, veröffentlicht Port nicht |
ENV |
Umgebungsvariablen setzen | ENV NODE_ENV=production |
Für Konfiguration verwenden |
USER |
Benutzer für nachfolgende Befehle festlegen | USER node |
Niemals als root in Produktion ausführen |
CMD |
Standardbefehl beim Container-Start | CMD ["node", "server.js"] |
JSON-Array-Format verwenden |
Layer-Caching verstehen
Docker erstellt Images Schicht für Schicht, von oben nach unten. Jede Anweisung erstellt eine neue Schicht. Wenn sich eine Schicht nicht geändert hat, verwendet Docker die gecachte Version, anstatt sie neu zu erstellen.
Deshalb sollten Sie Ihr Dockerfile so strukturieren, dass selten ändernde Anweisungen oben und häufig ändernde unten stehen. Zum Beispiel ändern sich Ihre Abhängigkeiten seltener als Ihr Quellcode, also installieren Sie Abhängigkeiten, bevor Sie Quelldateien kopieren.
# Schlecht: Kopiert erst alles, dann installiert
COPY . .
RUN npm install
# Gut: Installiert erst Abhängigkeiten, nutzt Cache
COPY package*.json ./
RUN npm install
COPY . .
Dockerfile Best Practices
Effiziente Dockerfiles zu schreiben ist eine Kunst. Hier ist ein produktionsreifes Beispiel, das mehrere Best Practices demonstriert:
# Multi-Stage-Build: Builder-Phase
FROM node:22-alpine AS builder
WORKDIR /app
# Abhängigkeitsdateien zuerst kopieren für besseres Caching
COPY package*.json ./
RUN npm ci --production
# Quellcode kopieren und bauen
COPY . .
RUN npm run build
# Multi-Stage-Build: Produktionsphase
FROM node:22-alpine
WORKDIR /app
# Nur das Nötige vom Builder kopieren
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
# Sicherheit: als Nicht-Root-Benutzer ausführen
USER node
# Port dokumentieren (veröffentlicht ihn nicht tatsächlich)
EXPOSE 3000
# Health Check für Container-Orchestrierung
HEALTHCHECK --interval=30s --timeout=3s \
CMD node healthcheck.js || exit 1
# Anwendung starten
CMD ["node", "dist/server.js"]
Multi-Stage-Builds: Dramatische Größenreduzierung
Multi-Stage-Builds ermöglichen es Ihnen, mehrere FROM-Anweisungen in einem Dockerfile zu verwenden. Jedes FROM startet eine neue Phase, und Sie können Artefakte aus vorherigen Phasen kopieren.
Die Magie geschieht, weil nur die letzte Phase zu Ihrem Image wird. Build-Tools, Compiler und Zwischendateien bleiben in früheren Phasen und gelangen nie in die Produktion. Dies kann die Image-Größe um das 10-fache oder mehr reduzieren.
Schneller Tipp: Benennen Sie Ihre Phasen mit AS builder, damit Sie sie später mit COPY --from=builder referenzieren können. Das macht Ihr Dockerfile lesbarer und wartbarer.
Alpine-Images: Klein aber mächtig
Alpine Linux ist eine minimale Linux-Distribution, die nur 5 MB groß ist. Vergleichen Sie das mit Ubuntu-basierten Images mit über 900 MB. Für die meisten Anwendungen bietet Alpine alles, was Sie brauchen.
Der Kompromiss ist, dass Alpine musl libc statt glibc verwendet, was gelegentlich Kompatibilitätsprobleme mit vorkompilierten Binärdateien verursachen kann. Für 95 % der Anwendungsfälle funktioniert Alpine perfekt und reduziert Image-Größe, Download-Zeit und Angriffsfläche dramatisch.
Die .dockerignore-Datei
Genau wie .gitignore teilt eine .dockerignore-Datei Docker mit, welche Dateien beim Erstellen von Images ausgeschlossen werden sollen. Dies beschleunigt Builds und reduziert die Image-Größe.
# .dockerignore Beispiel
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
dist
coverage
.vscode
.idea
*.log
.DS_Store
Sicherheits-Best-Practices
Sicherheit sollte von Anfang an in Ihre Dockerfiles eingebaut sein:
- Niemals als root ausführen: Verwenden Sie
USER nodeoder erstellen Sie einen dedizierten Benutzer - Spezifische Versionen festlegen: Verwenden Sie
node:22.1.0-alpinenichtnode:latest - Auf Schwachstellen scannen: Verwenden Sie
docker scan myimage, um nach bekannten CVEs zu suchen - Installierte Pakete minimieren: Weniger Software bedeutet weniger Schwachstellen
- Offizielle Basis-Images verwenden: Sie werden gewartet und regelmäßig aktualisiert
- Keine Geheimnisse in Images speichern: Verwenden Sie Umgebungsvariablen oder Secret-Management-Tools
Benötigen Sie Hilfe beim Generieren optimierter Dockerfiles? Probieren Sie unser Dockerfile-Generator-Tool aus.
Wichtige Docker-Befehle
Die Beherrschung dieser Befehle deckt 90 % Ihrer täglichen Docker-Nutzung ab. Jeder Befehl enthält praktische Beispiele und gängige Flags.
Erstellen und Ausführen
# Image aus Dockerfile im aktuellen Verzeichnis erstellen
docker build -t myapp:1.0 .
# Mit Build-Argumenten erstellen
docker build --build-arg NODE_ENV=production -t myapp:1.0 .
# Ohne Cache erstellen (Neuerstellung erzwingen)
docker build --no-cache -t myapp:1.0 .
# Container im Hintergrund ausführen
docker run -d -p 3000:3000 --name myapp myapp:1.0
# Mit Umgebungsvariablen ausführen
docker run -d -p 3000:3000 -e NODE_ENV=production --name myapp myapp:1.0
# Mit Volume-Mount ausführen
docker run -d -p 3000:3000 -v $(pwd)/data:/app/data --name myapp myapp:1.0
# Interaktiv mit Shell-Zugriff ausführen
docker run -it --rm myapp:1.0 sh
Container verwalten
# Laufende Container auflisten
docker ps
# Alle Container auflisten (einschließlich gestoppter)
docker ps -a
# Container-Logs anzeigen
docker logs myapp
# Logs in Echtzeit verfolgen
docker logs -f myapp
# Letzte 100 Zeilen der Logs anzeigen
docker logs --tail 100 myapp
# Befehl in laufendem Container ausführen
docker exec -it myapp sh
# Einmaligen Befehl ausführen
docker exec myapp npm test
# Container ordnungsgemäß stoppen
docker stop myapp
# Container sofort beenden
docker kill myapp
# Gestoppten Container entfernen
docker rm myapp
# In einem Befehl stoppen und entfernen
docker stop myapp && docker rm myapp
# Alle gestoppten Container entfernen
docker container prune
Mit Images arbeiten
# Alle Images auflisten
docker images
# Images mit benutzerdefiniertem Format auflisten
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# Image entfernen
docker rmi myapp:1.0
# Alle ungenutzten Images entfernen
docker image prune -a
# Image taggen
docker tag myapp:1.0 myapp:latest
# In Registry pushen
docker push myregistry.com/myapp:1.0
# Aus Registry pullen
docker pull myregistry.com/myapp:1.0
# Image in tar-Datei speichern
docker save myapp:1.0 > myapp.tar
# Image aus tar-Datei laden
docker load < myapp.tar
Systemverwaltung
# Festplattennutzung anzeigen
docker system df
# Alle ungenutzten Daten entfernen (Container, Netzwerke, Images, Cache)
docker system prune -a
# Echtzeit-Container-Statistiken anzeigen
docker stats
# Container-Details inspizieren (JSON-Ausgabe)
docker inspect myapp
# Container-Prozesse anzeigen
docker top myapp
Profi-Tipp: Fügen Sie das --rm-Flag hinzu, wenn Sie Container zum Testen ausführen. Dies entfernt den Container automatisch, wenn er stoppt, und hält Ihr System sauber: docker run --rm -it myapp:1.0 sh
Docker Compose für Multi-Container-Apps
Docker Compose ist ein Tool zum Definieren und Ausführen von Multi-Container-Anwendungen. Anstatt mehrere docker run-Befehle auszuführen, definieren Sie alles in einer einzigen YAML-Datei.
Dies ist essentiell für moderne Anwendungen, die typischerweise aus mehreren Services bestehen: einem Webserver, Datenbank, Cache, Message Queue und mehr.
Vollständiges Docker Compose-Beispiel
# docker-compose.yml
version: '3.8'
services:
# W