Docker für Entwickler: Container, Compose und praxisnahe Workflows
Ein praktischer Leitfaden zu Docker-Konzepten, Dockerfile-Best-Practices und dem Einsatz von Docker Compose für Multi-Service-Entwicklungsumgebungen.
„Auf meinem Rechner funktioniert es" – dieser Satz hat schon tausende fehlgeschlagene Deployments ausgelöst. Docker löst dieses Problem, indem es deine Anwendung und alle ihre Abhängigkeiten in einem Container verpackt – einer isolierten, reproduzierbaren Umgebung, die überall identisch läuft, vom Laptop bis zur Produktionsumgebung.
Container vs. virtuelle Maschinen
| Virtuelle Maschine | Container | |
|---|---|---|
| Isolation | Vollständiges OS | Prozessebene |
| Startzeit | Minuten | Sekunden |
| Größe | GBs | MBs |
| Overhead | Hoch (Hypervisor) | Nahezu null |
| Anwendungsfall | Vollständige OS-Isolation | App-Paketierung |
Container teilen sich den Host-Kernel, isolieren jedoch das Dateisystem, Prozesse und das Netzwerk. Deshalb sind sie so leichtgewichtig.
Grundlegende Docker-Konzepte
Image — Ein schreibgeschützter Bauplan für einen Container. Vergleichbar mit einer Klassendefinition.
Container — Eine laufende Instanz eines Images. Vergleichbar mit einem Objekt, das aus der Klasse erstellt wurde.
Registry — Ein System zur Speicherung und Verteilung von Images. Docker Hub ist der öffentliche Standard; GitHub Container Registry und AWS ECR sind gängige Alternativen.
Volume — Persistenter Speicher, der Container-Neustarts überlebt. Daten, die innerhalb eines Containers ohne Volume geschrieben werden, gehen verloren, wenn der Container stoppt.
Ein gutes Dockerfile schreiben
# Spezifische Version verwenden — niemals "latest" in der Produktion
FROM node:20-alpine
# Arbeitsverzeichnis setzen
WORKDIR /app
# Abhängigkeitsdateien zuerst kopieren (nutzt Layer-Caching)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Anwendungscode kopieren
COPY . .
# App bauen
RUN npm run build
# Aus Sicherheitsgründen als Nicht-Root-Benutzer ausführen
USER node
# Port dokumentieren (veröffentlicht ihn nicht)
EXPOSE 3000
# Exec-Form für korrekte Signal-Behandlung verwenden
CMD ["node", "server.js"]
Layer-Caching: der Schlüssel zu schnellen Builds
Jede Anweisung in einem Dockerfile erstellt einen Layer. Docker cached diese Layer – wenn sich an einem Layer oder seinen Vorgängern nichts geändert hat, verwendet Docker den Cache erneut.
Kopiere Abhängigkeitsdateien vor dem Anwendungscode. Abhängigkeiten ändern sich weit seltener als dein Code. Wenn du alles auf einmal kopierst, macht eine einzeilige Änderung in index.js den Cache der Abhängigkeitsinstallation ungültig und erzwingt ein vollständiges npm install.
Multi-Stage-Builds
Verwende Multi-Stage-Builds, um Produktions-Images schlank zu halten:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Produktion (keine Dev-Abhängigkeiten, kein Quellcode)
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
Das finale Image enthält nur die gebaute Ausgabe – nicht deinen Quellcode, Testdateien oder Dev-Abhängigkeiten.
Docker Compose für die lokale Entwicklung
Docker Compose orchestriert Multi-Container-Anwendungen. Eine einzige docker-compose.yml definiert deinen gesamten lokalen Stack:
version: "3.9"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
Generiere das Grundgerüst für deinen Stack mit unserem Docker Compose Generator — wähle deine Services und erhalte in Sekunden eine produktionsfertige docker-compose.yml.
Wichtige Compose-Befehle
# Alle Services im Hintergrund starten
docker compose up -d
# Logs anzeigen (Follow-Modus)
docker compose logs -f app
# Einen Befehl in einem laufenden Container ausführen
docker compose exec app sh
# Container stoppen und entfernen (Volumes bleiben erhalten)
docker compose down
# Container UND Volumes stoppen und entfernen
docker compose down -v
# Images nach Dockerfile-Änderungen neu bauen
docker compose up -d --build
Umgebungsvariablen und Secrets
Trage niemals Zugangsdaten fest in dein Dockerfile oder deine Compose-Datei ein. Verwende stattdessen eine .env-Datei:
# .env (zur .gitignore hinzufügen!)
DATABASE_URL=postgresql://user:pass@db:5432/myapp
REDIS_URL=redis://redis:6379
JWT_SECRET=your-super-secret-key
Compose lädt .env automatisch aus dem Projektverzeichnis. Referenziere Variablen mit ${VARIABLE_NAME} in deiner YAML-Datei.
Nutze unseren Env Generator, um .env-Dateien mit sinnvollen Standardwerten und .env.example-Vorlagen für dein Team zu erstellen.
Netzwerke in Docker
Compose erstellt ein Standard-Netzwerk für deinen Stack. Services können sich gegenseitig über den Service-Namen als Hostname erreichen – deshalb verwendet die App db als Datenbank-Host und nicht localhost.
app → verbindet sich mit "db:5432"
app → verbindet sich mit "redis:6379"
Nur Ports, die explizit mit ports: gemappt werden, sind vom Host-Rechner aus erreichbar.
Health Checks und Startreihenfolge
depends_on wartet nur darauf, dass ein Container gestartet wird, nicht darauf, dass er bereit ist. Ein Datenbank-Container startet in Sekunden, aber Postgres benötigt möglicherweise noch ein paar Sekunden, bevor er Verbindungen akzeptiert. Verwende healthcheck + condition: service_healthy, um zu warten, bis der Service tatsächlich bereit ist.
Überlegungen für die Produktion
- Spezifische Image-Tags verwenden —
node:20.11.1-alpine, nichtnode:latest. - Images auf Schwachstellen scannen —
docker scout cves myapp:latest. - Ressourcenlimits setzen — verhindere, dass ein einzelner Container andere aushungert.
- Schreibgeschützte Dateisysteme verwenden, wo möglich —
read_only: truein Compose. - Niemals als Root ausführen — füge
USER nodeoder ein Äquivalent hinzu. - Nginx als Reverse Proxy vor deiner App konfigurieren — nutze unseren Nginx Config Generator für eine bewährte Konfiguration.
Kurzreferenz
# Images
docker images # lokale Images auflisten
docker pull nginx:alpine # Image herunterladen
docker rmi myimage # Image entfernen
# Container
docker ps # laufende Container
docker ps -a # alle Container
docker stop <id> # geordnet stoppen
docker rm <id> # gestoppten Container entfernen
docker logs <id> -f # Logs streamen
# Aufräumen
docker system prune # alles Ungenutzte entfernen
docker volume prune # ungenutzte Volumes entfernen
Docker beseitigt eine ganze Kategorie umgebungsbedingter Fehler und macht das Onboarding neuer Entwickler zum Kinderspiel. Wer die Grundlagen hier beherrscht, hat ein solides Fundament für Kubernetes, CI/CD-Pipelines und Cloud-Deployments.