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.
Inhaltsverzeichnis
- 1. Die richtige Philosophie für Dev-Umgebungen
- 2. Bind-Mounts: Performance und Konsistenz
- 3. Hot-Reload für Backend und Frontend
- 4. Secrets und Umgebungsvariablen sicher verwalten
- 5. Override-Dateien: Dev vs. Prod trennen
- 6. Lokale DNS und Netzwerkzugriff komfortabel gestalten
- 7. Datenbankzustand im Development sinnvoll handhaben
- 8. Ein-Befehl-Onboarding für neue Teammitglieder
- 9. Dev-Umgebungs-Vergleich: Muster und Anti-Muster
- 10. Zusammenfassung
- 11. FAQ
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?
2Soll docker-compose.override.yml ins Repository?
3Wie verhindere ich Passwörter im Git?
4Wie funktioniert Hot-Reload für PHP?
validate_timestamps=1 und revalidate_freq=0 konfigurieren – sonst werden Änderungen nicht erkannt.5Wie richte ich lokales HTTPS ein?
6Wie verwalte ich verschiedene DB-Zustände?
make reset-db für Neuinitialisierung. docker compose down -v für vollständigen Reset inkl. aller Volumes.7Mehrere Projekte gleichzeitig in Docker Compose?
8Xdebug in Docker Compose integrieren?
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.