Docker Compose · Entwicklungsumgebung · DevEx · Team
Docker Compose für lokale Entwicklungsumgebungen
richtig entwerfen — von Tag eins an

Eine lokale Entwicklungsumgebung mit Docker Compose ist mehr als ein gestarteter Container. Wer Bind-Mounts falsch konfiguriert, verliert Performance. Wer Secrets in der Compose-Datei hardcoded, gefährdet das Projekt. Wer kein Onboarding-Konzept hat, verliert Stunden bei jedem neuen Teammitglied. Dieser Guide zeigt, wie man es von Anfang an richtig macht.

18 Min. Lesezeit Bind-Mounts · Hot-Reload · Secrets · Override · Onboarding Docker Compose v2 · macOS · Linux · Windows (WSL2)

1. Die richtige Philosophie für Dev-Umgebungen

Eine lokale Entwicklungsumgebung mit Docker Compose verfolgt ein anderes Ziel als eine Produktionsumgebung: Sie soll dem Entwickler eine schnelle Feedback-Schleife ermöglichen, alle benötigten Services auf Knopfdruck bereitstellen und gleichzeitig so wenig Reibung wie möglich erzeugen. Das bedeutet: Code-Änderungen sollen sofort sichtbar sein, der Start soll reproduzierbar und schnell sein, und die Konfiguration soll so einfach sein, dass sie ohne Einarbeitung verständlich ist.

Der fundamentale Unterschied zwischen einer guten und einer schlechten lokalen Entwicklungsumgebung ist oft nicht die technische Komplexität, sondern die Designentscheidungen beim initialen Aufbau. Eine Umgebung, die auf dem Rechner des ersten Entwicklers "einfach so" entstanden ist, trägt oft historischen Ballast – hartcodierte Pfade, fehlende Healthchecks, Secrets in Klartext. Das Entwerfen einer Entwicklungsumgebung von Grund auf mit Docker Compose ist eine Investition, die sich über die gesamte Projektlaufzeit auszahlt: durch schnelleres Onboarding, weniger "funktioniert bei mir nicht"-Situationen und eine Basis, die sich auch für CI-Testumgebungen wiederverwenden lässt.

Drei Prinzipien sollten die Architektur jeder lokalen Entwicklungsumgebung leiten: Erstens, die gesamte Umgebung muss mit einem einzigen Befehl startbar sein. Zweitens, kein Secret, kein Passwort und keine API-Key darf direkt in einer versionierten Datei stehen. Drittens, der Unterschied zwischen Entwicklungs- und Produktionskonfiguration muss explizit, nicht implizit sein – durch separate Dateien, nicht durch auskommentierte Blöcke.

2. Bind-Mounts: Performance und Konsistenz

Bind-Mounts sind der Kern jeder lokalen Entwicklungsumgebung mit Docker Compose: Sie mounten den lokalen Quellcode in den Container, sodass Code-Änderungen sofort im Container sichtbar sind. Auf Linux ist die Performance von Bind-Mounts nahezu identisch mit nativen Dateisystemzugriffen. Auf macOS und Windows (ohne WSL2) hingegen sind Bind-Mounts historisch ein Performance-Engpass, weil das Dateisystem über eine Virtualisierungsschicht kommuniziert.

Docker Desktop für macOS bietet seit Version 4.6 die VirtioFS-Implementierung, die Bind-Mount-Performance erheblich verbessert. Die korrekte Konfiguration in der Docker Compose-Datei für macOS-Optimierung: consistency: cached ist auf modernen Docker-Versionen deprecated und nicht mehr notwendig – VirtioFS übernimmt die Optimierung automatisch. Was weiterhin hilft: selektives Mounten statt des gesamten Projektverzeichnisses. node_modules, vendor und andere große Abhängigkeitsverzeichnisse sollten über ein anonymes Volume überschrieben werden, das im Container selbst lebt und nicht auf den Host synchronisiert wird.


# docker-compose.yml — Local development environment base configuration
# Optimized bind-mounts: override large dependency directories with anonymous volumes

services:
  app:
    build:
      context: .
      target: development         # Multi-stage: separate dev/prod image layers
    volumes:
      # Bind-mount: source code syncs bidirectionally with host
      - ./src:/var/www/html:rw
      # Anonymous volume: node_modules lives inside container only
      # Prevents slow cross-filesystem sync on macOS/Windows
      - /var/www/html/node_modules
      # Named volume: Composer vendor stays in container (fast installs)
      - vendor:/var/www/html/vendor
    environment:
      APP_ENV: development
      PHP_IDE_CONFIG: "serverName=docker"
    ports:
      - "8080:8080"               # HTTP
      - "9003:9003"               # Xdebug port
    networks:
      - dev-network

  db:
    image: mysql:8.4
    volumes:
      # Named volume: persistent between restarts, removed with docker compose down -v
      - db-data:/var/lib/mysql
      - ./docker/mysql/conf.d:/etc/mysql/conf.d:ro
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    ports:
      - "${DB_PORT:-3306}:3306"   # Configurable host port via .env
    networks:
      - dev-network

volumes:
  db-data:
  vendor:

networks:
  dev-network:
    driver: bridge

3. Hot-Reload für Backend und Frontend

Hot-Reload ist die wichtigste Eigenschaft einer produktiven lokalen Entwicklungsumgebung. Für Frontend-Assets mit Webpack, Vite oder Parcel bedeutet Hot-Reload, dass Änderungen an CSS- und JavaScript-Dateien sofort im Browser sichtbar sind, ohne einen manuellen Build-Schritt. Der entsprechende Dev-Server-Container läuft als eigener Service in Docker Compose und stellt seinen Port auf dem Host bereit.

Für PHP-Backends funktioniert Hot-Reload anders: Da PHP-Skripte bei jedem Request neu kompiliert werden, sind Code-Änderungen bei aktiviertem Bind-Mount sofort wirksam – solange der PHP-Opcode-Cache deaktiviert oder auf sofortige Invalidierung konfiguriert ist. In der lokalen Entwicklungsumgebung sollte OPcache mit opcache.validate_timestamps=1 und opcache.revalidate_freq=0 konfiguriert sein, damit Dateiänderungen sofort erkannt werden. Für Node.js-Backends mit Nodemon oder Go-Backends mit Air wird der Watcher-Prozess direkt im Container gestartet, der auf Dateiänderungen im gemounteten Quellcode-Verzeichnis reagiert.

4. Secrets und Umgebungsvariablen sicher verwalten

Das häufigste Sicherheitsproblem in lokalen Entwicklungsumgebungen ist das direkte Einbetten von Passwörtern und API-Keys in die docker-compose.yml. Diese Datei wird in der Regel ins Repository eingecheckt – und damit sind Credentials für jeden sichtbar, der Zugriff auf das Repository hat. Das korrekte Muster ist die strikte Trennung: Die Compose-Datei enthält nur Variablenreferenzen (${DB_PASSWORD}), die tatsächlichen Werte stehen in einer .env-Datei, die in .gitignore eingetragen ist.

Docker Compose liest automatisch eine .env-Datei im selben Verzeichnis wie die Compose-Datei und ersetzt Variablenreferenzen. Für das Team stellt man eine .env.example-Datei bereit, die alle notwendigen Variablen mit Platzhalterwerten enthält und ins Repository eingecheckt wird. Der Onboarding-Prozess enthält dann den Schritt: cp .env.example .env und anschließendes Ausfüllen der tatsächlichen Werte. Docker Compose Docker Secrets (secrets: in der Compose-Datei) sind primär für die Produktionsnutzung mit Docker Swarm konzipiert, können aber auch in der lokalen Entwicklungsumgebung für besonders sensitive Credentials wie private Keys oder Zertifikate eingesetzt werden.


# .env.example — checked into git, contains placeholder values only
# Copy to .env and fill in real values — .env is in .gitignore

APP_ENV=development
APP_KEY=base64:CHANGE_ME_generate_with_php_artisan_key_generate

DB_HOST=db
DB_PORT=3306
DB_NAME=myapp
DB_USER=myapp
DB_PASSWORD=CHANGE_ME_local_password_only
DB_ROOT_PASSWORD=CHANGE_ME_root_password_only

REDIS_HOST=redis
REDIS_PORT=6379

MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_FROM=dev@localhost

# External API keys — get from team password manager
STRIPE_KEY=sk_test_CHANGE_ME
STRIPE_SECRET=CHANGE_ME

# Xdebug configuration
XDEBUG_MODE=debug
XDEBUG_CLIENT_HOST=host.docker.internal
XDEBUG_CLIENT_PORT=9003

5. Override-Dateien: Dev vs. Prod trennen

Das Override-System von Docker Compose ermöglicht eine saubere Trennung zwischen Basis-Konfiguration und umgebungsspezifischen Anpassungen. Die Basis-Datei docker-compose.yml definiert die Service-Struktur, Images und grundlegende Konfiguration. Eine docker-compose.override.yml wird von Docker Compose automatisch eingelesen und überschreibt oder ergänzt die Basis-Konfiguration. Diese Datei enthält entwicklungsspezifische Einstellungen: Bind-Mounts für den Quellcode, Xdebug-Konfiguration, Debug-Logging und lokale Port-Weiterleitungen.

Da docker-compose.override.yml ebenfalls ins Repository eingecheckt werden sollte (sie enthält keine Secrets, nur Strukturkonfiguration für die lokale Entwicklungsumgebung), ist sie der richtige Ort für alles, was für die lokale Arbeit, aber nicht für die Produktion gilt. Eine separate docker-compose.prod.yml enthält produktionsspezifische Konfigurationen wie Health-Probes für Kubernetes, Resource-Limits und ReadOnly-Filesystems. Die explizite Angabe beider Dateien beim Deploy-Befehl (docker compose -f docker-compose.yml -f docker-compose.prod.yml up) macht die Zusammensetzung transparent und nachvollziehbar.

6. Lokale DNS und Netzwerkzugriff komfortabel gestalten

Der Zugriff auf Services in der lokalen Entwicklungsumgebung über sprechende Hostnamen statt localhost:8080 verbessert die Developer Experience erheblich. Tools wie Traefik als Reverse-Proxy-Container in Docker Compose ermöglichen es, Services über lokale Hostnamen wie app.local.mironsoft.de erreichbar zu machen. Traefik liest Labels aus den Service-Definitionen und generiert daraus automatisch Routing-Regeln.

Für lokale HTTPS-Zertifikate ist mkcert der etablierte Standard: Es erstellt selbstsignierte Zertifikate, die vom Browser als vertrauenswürdig akzeptiert werden, weil mkcert eine lokale CA in den Browser-Zertifikatspeicher einträgt. Das resultierende Zertifikat wird als Volume in den Traefik-Container eingebunden. Das Ergebnis ist eine lokale Entwicklungsumgebung, die sich exakt wie die Produktionsumgebung verhält – mit HTTPS, korrekten Hostnamen und ohne Browser-Warnungen – während der gesamte Traffic lokal bleibt.


# docker-compose.override.yml — development-only additions
# Automatically merged by Docker Compose with docker-compose.yml

services:
  app:
    volumes:
      # Bind-mount for live code sync
      - ./src:/var/www/html:rw
      - /var/www/html/vendor          # Keep vendor inside container
    environment:
      XDEBUG_MODE: ${XDEBUG_MODE:-debug}
      XDEBUG_CLIENT_HOST: host.docker.internal
      PHP_OPCACHE_VALIDATE_TIMESTAMPS: "1"
      PHP_OPCACHE_REVALIDATE_FREQ: "0"
    labels:
      # Traefik routing labels for local DNS
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.localhost`)"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

  # Local mail catcher — captures all outgoing mail
  mailpit:
    image: axllent/mailpit:latest
    ports:
      - "8025:8025"                   # Web UI for viewing captured emails
    networks:
      - dev-network

  # Traefik reverse proxy with local HTTPS
  traefik:
    image: traefik:v3.1
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./docker/traefik/certs:/certs:ro
    networks:
      - dev-network

7. Datenbankzustand im Development sinnvoll handhaben

Die Datenbankstrategie in der lokalen Entwicklungsumgebung muss zwei gegensätzliche Anforderungen ausbalancieren: Persistenz für die tägliche Arbeit (Entwickler wollen nicht bei jedem Start eine leere Datenbank haben) und die Möglichkeit, bei Bedarf auf einen definierten Ausgangszustand zurückzusetzen. Named Volumes in Docker Compose bieten Persistenz zwischen Container-Restarts, werden aber mit docker compose down -v vollständig gelöscht.

Das empfohlene Muster für die lokale Entwicklungsumgebung: Eine Seed-Skript-Sammlung, die realistische (aber anonymisierte) Testdaten erzeugt und über einen Makefile-Befehl oder ein npm-Script aufrufbar ist. Regelmäßige Datenbankdumps aus der Staging-Umgebung, die über ein shared Volume oder ein internes Repository bereitgestellt werden, halten die lokale Datenbank mit realistischen Daten aktuell. Für die Arbeit an Migrationen ist ein separates Compose-Override nützlich, das die Datenbank mit einem bekannten Schema-Stand startet, auf dem neue Migrationen entwickelt werden können.

8. Ein-Befehl-Onboarding für neue Teammitglieder

Das Ziel eines guten Docker Compose-Setups für die lokale Entwicklung ist, dass ein neues Teammitglied nach dem Klonen des Repositories mit einem einzigen Befehl eine vollständige, funktionsfähige Entwicklungsumgebung hat. In der Praxis bedeutet das: Es gibt ein Setup-Script oder Makefile-Target, das .env.example nach .env kopiert (wenn noch nicht vorhanden), alle Images baut, alle Services startet, Datenbankmigrationen ausführt und eine Seed-Datenbank einspielt.

Die lokale Entwicklungsumgebung dokumentiert sich am besten durch den Code selbst: Eine docker-compose.yml mit sprechenden Service-Namen, kommentierten Port-Weiterleitungen und einer .env.example mit erklärenden Kommentaren zu jeder Variable ist besser als ein veraltetes README. Ein Makefile als zentraler Einstiegspunkt für häufige Operationen (make up, make down, make reset-db, make shell) senkt die Einstiegshürde und versteckt komplexe Docker-Compose-Befehle hinter einfachen, selbstdokumentierenden Targets.


# Makefile — single entry point for all common development operations
# Usage: make up | make down | make shell | make reset-db | make logs

.PHONY: up down shell logs reset-db test setup

# First-time setup: copies .env, builds images, starts services, runs migrations
setup:
	@[ -f .env ] || cp .env.example .env
	@echo "Please fill in .env values if this is your first run."
	@docker compose build --parallel
	$(MAKE) up
	@docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction
	@docker compose exec app php bin/console doctrine:fixtures:load --no-interaction
	@echo "Setup complete. App available at https://app.localhost"

# Start all services in background
up:
	docker compose up --detach --remove-orphans

# Stop all services (keep volumes)
down:
	docker compose down

# Open shell in app container
shell:
	docker compose exec app bash

# Follow all service logs
logs:
	docker compose logs --follow --tail=100

# Drop and recreate database with fresh seed data
reset-db:
	docker compose exec app php bin/console doctrine:database:drop --force
	docker compose exec app php bin/console doctrine:database:create
	docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction
	docker compose exec app php bin/console doctrine:fixtures:load --no-interaction
	@echo "Database reset complete."

# Run test suite
test:
	docker compose exec app php vendor/bin/phpunit

9. Dev-Umgebungs-Vergleich: Muster und Anti-Muster

Die häufigsten Fehler beim Entwurf lokaler Entwicklungsumgebungen mit Docker Compose sind gut dokumentiert und wiederholen sich in vielen Projekten. Der Vergleich zwischen Anti-Mustern und empfohlenen Mustern zeigt, wo die größten Hebel für Verbesserungen liegen.

Bereich Anti-Muster Empfohlenes Muster Warum
Secrets Passwörter in docker-compose.yml .env + .env.example Credentials nie in VCS
Vendor-Mount vendor/ als Bind-Mount Anonymes Volume überschreibt Bind Massiv schnellere PHP-Performance
Onboarding 10-seitiges README make setup automatisiert alles Reproduzierbar, fehlerresistent
Dev vs. Prod Eine Compose-Datei für alles Base + override.yml Explizite Trennung der Kontexte
Mail in Dev Echter SMTP oder kein Mail Mailpit-Container Alle Mails lokal abfangen

Der größte Performance-Gewinn bei lokalen Entwicklungsumgebungen auf macOS kommt regelmäßig aus einem einzigen Change: das Überschreiben des Bind-Mounts für vendor/ und node_modules/ durch anonyme Volumes. Diese Verzeichnisse enthalten tausende kleine Dateien – genau die Art von Workload, bei der Bind-Mounts auf macOS langsam sind. Nach dieser Änderung berichten Teams typischerweise von einer Verbesserung der Page-Load-Zeit in der Entwicklungsumgebung um Faktor 3–10.

Mironsoft

Docker Compose, Developer Experience und lokale Entwicklungsumgebungen

Lokale Entwicklungsumgebung, die das Team wirklich nutzt?

Wir entwerfen Docker Compose-Entwicklungsumgebungen, die sich in Sekunden starten, performante Bind-Mounts haben, Secrets sicher verwalten und neuen Entwicklern den sofortigen Einstieg ermöglichen.

Dev-Umgebungs-Setup

Docker Compose mit Bind-Mount-Optimierung, Hot-Reload und lokalem HTTPS

Secrets-Management

.env-Strategie, .env.example und sichere Credentials-Verteilung im Team

Onboarding-Automatisierung

make setup als Ein-Befehl-Einstieg mit Migration, Seed und Healthcheck-Prüfung

10. Zusammenfassung

Eine professionell entworfene lokale Entwicklungsumgebung mit Docker Compose ist keine Frage des Aufwands, sondern der richtigen Designentscheidungen. Bind-Mounts für Quellcode, anonyme Volumes für Abhängigkeitsverzeichnisse, .env-Dateien für Secrets und Override-Dateien für umgebungsspezifische Konfiguration bilden das solide Fundament. Hot-Reload für Frontend und Backend, lokales HTTPS mit Traefik und mkcert sowie ein Mail-Catcher wie Mailpit sorgen für eine Developer Experience, die der Produktion entspricht, ohne sie zu imitieren.

Der entscheidende Qualitätsmaßstab für jede lokale Entwicklungsumgebung: Wie lange dauert das Onboarding eines neuen Teammitglieds? Wenn die Antwort "ein Tag" ist, gibt es Verbesserungspotenzial. Mit den richtigen Docker Compose Mustern – make setup, vollständige Automatisierung, selbstdokumentierende Konfiguration – sollte das Ziel ein einziger Befehl und wenige Minuten sein. Das ist keine Übertreibung, sondern eine realistische Erwartung an moderne Entwicklungsinfrastruktur.

Lokale Entwicklungsumgebung mit Docker Compose — Das Wichtigste auf einen Blick

Bind-Mount-Performance

vendor/ und node_modules/ als anonyme Volumes überschreiben – kein Bind-Mount für große Abhängigkeitsverzeichnisse, besonders auf macOS entscheidend.

Secrets-Strategie

.env in .gitignore, .env.example im Repository. Keine Passwörter in docker-compose.yml oder docker-compose.override.yml.

Override-System

docker-compose.override.yml für Dev-spezifische Konfiguration. Wird automatisch gemergt. Explizite Trennung von Basis und Umgebungskonfiguration.

Ein-Befehl-Onboarding

make setup automatisiert: .env-Kopie, Image-Build, Start, Migration, Seed. Ziel: neues Teammitglied in unter 10 Minuten produktiv.

11. FAQ: Docker Compose für lokale Entwicklungsumgebungen

1Warum ist Docker Compose auf macOS langsamer?
Bind-Mounts laufen über eine Virtualisierungsschicht. VirtioFS verbessert das erheblich. Anonyme Volumes für vendor/ und node_modules/ umgehen das Problem.
2Soll docker-compose.override.yml ins Repository?
Ja, wenn keine Secrets drin sind. Bind-Mounts, Debug-Config, lokale Ports – alles Team-gemeinsam. Dev-spezifische Anpassungen in docker-compose.local.yml (gitignored).
3Wie verhindere ich Passwörter im Git?
.env in .gitignore. Nur ${VARIABLE}-Referenzen in Compose-Dateien. .env.example mit Platzhalterwerten bereitstellen. Pre-commit Hook hinzufügen.
4Wie funktioniert Hot-Reload für PHP?
PHP lädt Skripte bei jedem Request neu. OPcache mit validate_timestamps=1 und revalidate_freq=0 konfigurieren – sonst werden Änderungen nicht erkannt.
5Wie richte ich lokales HTTPS ein?
mkcert erstellt Browser-vertrauenswürdige Zertifikate für *.localhost. Traefik in Docker Compose terminiert TLS. Alle internen Services ohne TLS.
6Wie verwalte ich verschiedene DB-Zustände?
Named Volume für Persistenz. make reset-db für Neuinitialisierung. docker compose down -v für vollständigen Reset inkl. aller Volumes.
7Mehrere Projekte gleichzeitig in Docker Compose?
Verschiedene COMPOSE_PROJECT_NAME und Host-Ports. Traefik als gemeinsamer Reverse-Proxy mit lokalem DNS eliminiert Port-Konflikte vollständig.
8Xdebug in Docker Compose integrieren?
Port 9003 exponieren. XDEBUG_CLIENT_HOST=host.docker.internal. IDE auf Listen-Modus. XDEBUG_MODE über .env konfigurierbar machen.
9up --build vs. docker compose build?
build baut ohne zu starten. up --build baut und startet. Tägliches Arbeiten ohne --build – neu bauen nur bei Dockerfile-Änderungen.
10Netzwerkprobleme in Docker Compose debuggen?
docker compose exec service ping anderer-service prüft DNS. docker network inspect zeigt alle verbundenen Container und IPs.