Developer Tools

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.

9 Min. Lesezeit

Serverraum mit blauer Beleuchtung

„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

  1. Spezifische Image-Tags verwendennode:20.11.1-alpine, nicht node:latest.
  2. Images auf Schwachstellen scannendocker scout cves myapp:latest.
  3. Ressourcenlimits setzen — verhindere, dass ein einzelner Container andere aushungert.
  4. Schreibgeschützte Dateisysteme verwenden, wo möglich — read_only: true in Compose.
  5. Niemals als Root ausführen — füge USER node oder ein Äquivalent hinzu.
  6. 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.