Zurück zu allen Beiträgen
·4 Min. Lesezeit

Eine reproduzierbare Entwicklungsumgebung mit Docker Compose bauen

In einem Open-Source-Projekt ist „Es funktioniert auf meinem Rechner“ meist kein technischer Erfolg. Es zeigt eine Lücke in Dokumentation und Umgebungsvertrag. Muss ein neuer Entwickler nach dem Klonen des Repositories raten, welche Version installiert wird, welcher Dienst startet, wie die Datenbank vorbereitet wird oder wo ein geheimer Wert herkommt, ist das Projekt noch nicht leicht einzurichten.

Docker Compose löst dieses Problem nicht allein. Eine compose.yaml kann einige Container starten, aber eine reproduzierbare Entwicklungsumgebung macht Abhängigkeiten, Konfiguration, Datenlebensdauer und den ersten Start zu einem expliziten Vertrag. Der Test ist einfach: Kann die Anwendung auf einer sauberen Maschine mit wenigen dokumentierten Schritten nutzbar werden?

Den Einstieg gestalten

Die erste Aufgabe einer Entwicklungsumgebung ist nicht, eine funktionierende Anwendung zu beschreiben. Sie soll jemanden, der bei null beginnt, sicher dorthin bringen. Ein guter Startablauf folgt meist dieser Reihenfolge:

  1. Repository klonen und Beispielkonfiguration kopieren.
  2. Alle ausdrücklich benötigten lokalen Werte ausfüllen.
  3. Anwendung und Abhängigkeiten mit einem Befehl starten.
  4. Über einen Health Check oder kurzen Prüfkommando sehen, dass das System bereit ist.
  5. Wenn Testdaten nötig sind, einen optionalen Seed-Befehl nutzen, der gefahrlos erneut laufen kann.

Jeder manuelle Schritt in diesem Ablauf verdient Prüfung. Eine Datenbank als Service zu definieren, ist konsistenter, als jemanden PostgreSQL separat installieren zu lassen. Migrationen beim Start oder in einem expliziten Task-Befehl zu platzieren, ist sicherer, als Beitragende eine SQL-Datei einmal manuell ausführen zu lassen.

Services nach ihrem Zweck trennen

Nicht alles muss ein eigener Container sein. Anwendung, Datenbank, Cache, Queue, Objektspeicher oder E-Mail-Testwerkzeug werden als getrennte Services nützlich, wenn die Trennung Abhängigkeitsversionen, persistente Daten und Netzwerkzugriff sichtbar macht.

Umgekehrt können Services, die nur für einen Prozess getrennt wurden, die Entwicklung erschweren. Maßstab ist nicht technische Reinheit, sondern der tägliche Arbeitsablauf. Kann ein Entwickler Anwendungscode aktualisieren, ohne eine Abhängigkeit neu zu starten? Findet das Frontend das Backend unter einer konsistenten lokalen Adresse? Kann ein Test alles Nötige erreichen, ohne das öffentliche Internet zu benötigen?

Namen sollten eher die Funktion als ein Infrastrukturdetaill beschreiben. Namen wie api, web, db, cache und worker werden im Compose-Netzwerk zugleich zu verständlichen DNS-Namen.

Konfiguration von Geheimnissen trennen

Ports, Log-Level, aktivierte Features und lokale URLs lassen sich mit Umgebungsvariablen konfigurieren. Echte Zugriffsschlüssel, Passwörter und Produktions-Connection-Strings dürfen niemals in das Repository gelangen. Stattdessen sollte eine .env.example für jede benötigte Variable eine sichere Vorlage mit Erklärung anbieten.

Die Beispieldatei sollte ein ausführbarer Startpunkt sein, nicht nur eine Liste leerer Werte. Für lokal gehostete Services können sinnvolle Standardwerte vorhanden sein; Werte, die ein eigenes Provider-Konto erfordern, sollten klar markiert werden. Beim Start sollten fehlende Pflichtvariablen verständlich gemeldet werden.

Häufig erscheint dieselbe Einstellung unter unterschiedlichen Namen in Compose-Datei und Anwendungskonfiguration. Namen und Verantwortlichkeit an einer Stelle zu dokumentieren, reduziert kleine, aber zeitaufwendige Fragen wie: Welcher Service besitzt einen Port?

Bereitschaft von Startreihenfolge trennen

Ein gestarteter Container bedeutet nicht automatisch, dass eine Anwendung Anfragen annehmen kann. Ein Datenbankprozess kann laufen, ohne Verbindungen anzunehmen; Migrationen können noch arbeiten; ein Worker kann noch nicht mit seiner Queue verbunden sein.

Verwenden Sie Health Checks, statt sich nur auf die Startreihenfolge zu verlassen. Nimmt die Datenbank tatsächlich Abfragen an? Antwortet die API nach den Migrationen auf ihren Health Endpoint? Abhängige Services sollten nach Möglichkeit auf gesunde Zustände warten. Die Anwendung braucht trotzdem eigene Retry-Logik; ein Compose Health Check darf nicht die einzige Verteidigung gegen temporäre Netzwerkfehler sein.

Wenn ein erster Start scheitert, müssen Logs lesbar sein. Über „Connection refused“ hinaus sollte ein Beitragender verstehen, welche Abhängigkeit erwartet wurde, welche Verbindungsdaten verwendet wurden und was der nächste sichere Schritt ist.

Datenlebensdauer bewusst verwalten

Daten aus Datenbank und Dateispeicher bei jedem down zu löschen, kostet Zeit und erzeugt ein falsches Sicherheitsgefühl. Benannte Volumes halten Daten vom Containerlebenszyklus getrennt. Diese Persistenz muss dokumentiert sein: Was löscht ein Befehl für eine saubere Umgebung, kann Seed-Daten erneut ausgeführt werden und lässt sich eine Migration zurückrollen?

Entwicklungs- und Testdaten sollten von echten personenbezogenen Daten getrennt werden. Ein lokaler Beispieldatensatz darf kein kopiertes Produktionsbackup sein. Anonymisierte oder synthetische Datensätze lassen sich leichter teilen und machen Demo- sowie Testabläufe sicherer.

Profile für optionale Abhängigkeiten nutzen

Nicht jeder Entwickler benötigt gleichzeitig eine Suchmaschine, Message Queue, Objektspeicher oder einen E-Mail-Catcher. Compose-Profile halten die Basisumgebung leicht und ermöglichen zusätzliche Services für bestimmte Features.

Ein Standardbefehl kann beispielsweise api, web und db starten, während Integrationstests ein Profil mit queue und mail ergänzen. Diese Unterscheidung sollte auch in der Dokumentation sichtbar sein. Hängt ein Feature von einem Profil ab, ist eine Einrichtungsanleitung besser als ein unerklärlicher Fehler in der Oberfläche.

Dokumentation mit Befehlen prüfen

Jeder Einrichtungsbefehl in einer README sollte regelmäßig in einer echten Umgebung laufen. Der stärkste Test besteht darin, die Quick-Start-Schritte mit einem frischen Klon und sauberen Volumes zu befolgen. Das deckt fehlende .env-Variablen, vergessene Migrationen und versteckte Annahmen über externe Werkzeuge auf.

Eine Compose-Datei ist nicht die gesamte Entwicklungsumgebung. Gut gestaltet senkt sie aber die Hürde für den ersten Beitrag erheblich. Klare Service-Grenzen, sichere Konfiguration, Health Checks, bewusste Datenlebensdauer und geprüfte Dokumentation machen aus „es funktioniert“ kein persönliches Ereignis, sondern ein reproduzierbares Versprechen.