Docker für Entwickler: Container, Images und Compose

· 12 Min. Lesezeit

Inhaltsverzeichnis

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:

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:

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:

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