Website-Suche

Der einfachste Weg, eine lokale Entwicklungs-Toolbox mit einer Datei zu erstellen


Jeder Entwickler, den ich kenne, hat denselben Zyklus durchlaufen. Man beginnt ein neues Projekt und benötigt eine Datenbank. Also lädt man PostgreSQL oder MySQL herunter, durchläuft die Konfiguration und vergisst das Passwort, das man fünf Minuten zuvor gesetzt hat. Einige Monate später beginnt man ein anderes Projekt, das eine andere Version dieser Datenbank benötigt oder vielleicht Redis, und plötzlich ist der Host-Rechner ein Friedhof von Diensten, die man einmal installiert und dann vergessen hat.

Es gibt viele Möglichkeiten, dieses Problem zu lösen (ehrlich gesagt viel zu viele), aber für eine persönliche lokale Entwicklungsumgebung ist die einfachste und am besten wartbare Lösung, alles in eine einzige Docker-Compose-Datei zu packen.

Die Anatomie eines lokalen Entwickler-Werkzeugkastens

Die Dienste, die die meisten Entwicklungsumgebungen benötigen

Wenn ich mich hinsetze, um an einer Webanwendung zu arbeiten, gibt es einige Dienste, die ich fast immer benötige. Die meisten Entwickler verlassen sich auf ein ähnliches Set von Werkzeugen, auch wenn die genauen Anforderungen von Projekt zu Projekt variieren. Ich brauche eine Datenbank, um Daten zu speichern, in der Regel PostgreSQL. Oft benötige ich einen Key-Value-Store wie Redis für Caching oder Sitzungsverwaltung. Wenn ich an etwas arbeite, das Hintergrundaufgaben oder ereignisgesteuerte Architektur beinhaltet, brauche ich einen Message-Broker wie RabbitMQ, und um die E-Mail-Funktionalität während der Entwicklung zu testen, möchte ich etwas wie MailHog, das ausgehende E-Mails abfängt und mir in einer Weboberfläche zeigt, anstatt sie tatsächlich zu versenden.

Wir werden eine Docker-Compose-Datei erstellen, die all diese Dienste zusammen ausführt. Diese Datei wird zu Ihrem tragbaren Entwicklungstoolbox. Sie können sie von Projekt zu Projekt kopieren, die Versionen anpassen und jedes Mal eine konsistente Umgebung haben.

Erstellen Sie zunächst ein neues Verzeichnis für Ihr Projekt und erstellen Sie in diesem Verzeichnis eine Datei mit dem Namen docker-compose.yml. Dies ist der Standardname und danach sucht der Befehl docker compose standardmäßig.

1. Beginnend mit der Datenbank

Lassen Sie uns mit PostgreSQL beginnen. Hier ist eine grundlegende Servicedefinition für PostgreSQL:

services:
  postgres:
    image: postgres:18.2
    container_name: local_postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: myapp_dev
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

In diesem kleinen Block passiert viel, also lassen Sie mich jede Zeile erklären. Der Dienste-Block ist der Ort, an dem Sie alle Container auflisten, die Sie ausführen möchten. Jeder Dienst hat einen Namen, in diesem Fall postgres.

Die Image-Zeile sagt Docker, welches Image von Docker Hub gezogen werden soll. Ich habe es auf postgres:18.2 festgesetzt. Wenn Sie einfach postgres ohne Tag verwenden, erhalten Sie den neuesten Tag, was bedeutet, dass sich Ihre Umgebung unerwartet ändern könnte, wenn eine neue Hauptversion von PostgreSQL veröffentlicht wird (es bricht oft Dinge).

Der container_name ist optional, aber hilfreich. Er legt einen festen Namen für den Container fest. Ohne diesen würde Docker einen zufälligen Namen wie postgres_1234abcd generieren. Einen vorhersehbaren Namen zu haben, erleichtert es, Docker-Befehle gegen diesen spezifischen Container auszuführen, falls dies erforderlich ist.

Die Port-Zeile ordnet einen Port auf Ihrem Host-Rechner einem Port innerhalb des Containers zu. Das Format ist „host:container“. PostgreSQL hört standardmäßig auf Port 5432 innerhalb des Containers. Indem Sie ihn auf denselben Port auf Ihrem Host abbilden, können Sie über localhost:5432 aus jedem Tool, das auf Ihrem Laptop läuft, auf die Datenbank zugreifen, genau so, als hätten Sie PostgreSQL nativ installiert.

Der Bereich Environment setzt Umgebungsvariablen innerhalb des Containers. Das PostgreSQL-Image sucht nach diesen spezifischen Variablen, um den Standardbenutzer, das Passwort und die Datenbank zu konfigurieren. Ich habe hier nur einfache Werte verwendet, nur für die Demo.

Der Abschnitt über Volumes ist entscheidend, da Container standardmäßig flüchtig sind. Wenn Sie den Container löschen, werden auch Ihre Daten gelöscht (für immer, kein Papierkorb oder Recycling-Ordner). Die Zeile postgres_data: /var/lib/postgresql/data erstellt ein benanntes Volume namens postgres_data und bindet es an den Pfad innerhalb des Containers, in dem PostgreSQL seine Datendateien speichert. Das bedeutet, dass Ihre Datenbank Container-Neustarts und sogar das Löschen des Containers überlebt. Wir werden das Volume am Ende der Datei definieren.

Schließlich teilt restart: unless-stopped Docker mit, den Container automatisch neu zu starten, wenn er abstürzt, ihn jedoch nicht neu zu starten, wenn Sie ihn manuell stoppen.

2. Redis hinzufügen

Als Nächstes fügen wir Redis hinzu. Redis ist im Vergleich zu PostgreSQL einfach. Es hat kein komplexes Benutzersystem und benötigt keine Umgebungsvariablen für die Einrichtung.

redis:
    image: redis:8.6.1
    container_name: local_redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

3. Integration von RabbitMQ

RabbitMQ ist etwas aufwendiger, weil es eine Management-Benutzeroberfläche hat. Wir möchten Zugriff auf diese Benutzeroberfläche in unserem Browser haben.

rabbitmq:
  image: rabbitmq:4.2-management-alpine
  container_name: local_rabbitmq
  ports:
    - "5672:5672"
    - "15672:15672"
  environment:
    RABBITMQ_DEFAULT_USER: guest
    RABBITMQ_DEFAULT_PASS: guest
  volumes:
    - rabbitmq_data:/var/lib/rabbitmq
  restart: unless-stopped

Derzeit sind zwei Ports zugewiesen. Port 5672 ist der Haupt-AMQP-Port, den Ihre Anwendung zum Verbinden mit RabbitMQ für das Senden und Empfangen von Nachrichten verwendet. Port 15672 ist der HTTP-Port für die Web-Management-Oberfläche. Sie können Ihren Browser unter http://localhost:15672 öffnen und sich mit den Standardzugangsdaten guest/guest anmelden, um Warteschlangen, Exchanges und Nachrichten anzuzeigen.



4. E-Mails mit MailHog abfangen

MailHog ist ein Tool, das einen gefälschten SMTP-Server ausführt und alle an ihn gesendeten Nachrichten erfasst. Es bietet eine Weboberfläche, um diese erfassten E-Mails anzusehen. Es ist unschätzbar wertvoll für das Testen von Registrierungsabläufen, Passwortzurücksetzungen und Benachrichtigungs-E-Mails, ohne versehentlich echte Benutzer zu spammen.

  mailhog:
    image: minidocks/mailhog
    container_name: local_mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    restart: unless-stopped

Am Ende Ihrer docker-compose.yml-Datei müssen Sie die benannten Volumes deklarieren, die Sie verwendet haben:

volumes:
  postgres_data:
  redis_data:
  rabbitmq_data:

Dies sagt Docker, diese Volumes zu erstellen und zu verwalten. Docker speichert die tatsächlichen Daten irgendwo auf Ihrem Host-Dateisystem, aber Sie müssen nicht wissen, wo.

5. Alles zusammenfügen

# Local Dev Toolbox
services:
  postgres:
    image: postgres:18.2
    container_name: local_postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: myapp_dev
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: redis:8.6.1
    container_name: local_redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

  rabbitmq:
    image: rabbitmq:4.2-management-alpine
    container_name: local_rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    restart: unless-stopped

  mailhog:
    image: minidocks/mailhog
    container_name: local_mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  rabbitmq_data:

Speichern Sie diese Datei in Ihrem Projektverzeichnis.

Ihre Toolbox ausführen

1. Öffnen Sie ein Terminal in dem Verzeichnis, das Ihre docker-compose.yml-Datei enthält. Um alles zu starten, führen Sie aus:

docker compose up -d 

Die -d-Option führt die Container im Hintergrundmodus aus, das heißt, sie laufen im Hintergrund, und Sie erhalten Ihre Terminalaufforderung zurück.

2. Um zu sehen, was läuft, verwenden Sie:

docker compose ps

Dies zeigt Ihnen den Status jedes Containers, die Ports, auf die sie abgebildet sind, und wie lange sie bereits laufen.



3. Wenn Sie Ihre Arbeit für den Tag beendet haben, können Sie die Dienste stoppen, ohne sie zu zerstören:

docker compose stop

Dies stoppt die Container, lässt sie aber und ihre Datenträger unverändert. Sie können sie später wieder mit docker compose start oder docker compose up starten.

4. Wenn Sie die Container vollständig entfernen, aber Ihre Datenvolumes behalten möchten, verwenden Sie:

docker compose down

5. Um alles zu entfernen, einschließlich der Volumes und aller darin enthaltenen Daten, fügen Sie das -v-Flag hinzu:

docker compose down -v



Alternativen zu Docker Compose

Dienste direkt auf Ihrem System installieren

Ist eine Compose-Datei der einzige Weg, um eine lokale Entwickler-Toolbox zu erstellen? Natürlich nicht. Genauso wie es unzählige Linux-Distributionen gibt, gibt es viele Möglichkeiten, einen Entwickler-Stack zu erstellen.

Der traditionellste Ansatz besteht darin, alles direkt auf Ihrem Betriebssystem zu installieren. Sie installieren PostgreSQL über Ihren Paketmanager, aktivieren den Dienst und machen weiter. Im weiteren Sinne ist selbst die Installation von etwas wie build-essential auf einem debianbasierten System bereits der Beginn eines Entwicklungs-Stacks. Sie holen Compiler, Header und Kernwerkzeuge in Ihr Basissystem, damit Software lokal kompiliert und ausgeführt werden kann.

Dies funktioniert gut für einfache Fälle, aber es wird kompliziert, wenn Projekte sich zu unterscheiden beginnen. Wenn eine Anwendung PostgreSQL 18 benötigt und eine andere auf PostgreSQL 16 angewiesen ist, ist man gezwungen, mehrere Instanzen auf verschiedenen Ports zu jonglieren oder verschiedene Versionen neu zu installieren. Die Verwaltung als native Systemdienste wird mühsam. Das spätere Entfernen ist selten so sauber wie erwartet, und Konfigurationsdateien bleiben oft verstreut im System zurück.

Versionsmanager wie asdf

Werkzeuge wie asdf sind großartig, um Sprach-Runtimes zu verwalten, und machen das Wechseln zwischen verschiedenen Versionen von Python, Node.js oder Ruby sehr reibungslos. Für Anwendungs-Code löst dies tatsächlich ein echtes Problem.

Datenbanken, Message Broker und Mailserver passen jedoch nicht sauber in dieses Modell. Sie sind keine leichten Laufzeit-Binärdateien, die man einfach mit einem Shim austauscht. Sie benötigen persistente Datenverzeichnisse, Hintergrundprozesse und Systemressourcen. Man könnte sie manuell kompilieren, aber das würde das Ziel einer schnellen und reproduzierbaren Einrichtung zunichte machen.

Automatisierungstools wie Ansible und Taskfiles

Eine weitere Möglichkeit ist die Verwendung von Automatisierungs-Frameworks und Task-Runnern. Werkzeuge wie Ansible sind sehr leistungsfähig für das Bereitstellen von Servern. Ebenso ermöglichen Task-Runner wie Task das Orchestrieren komplexer Befehlssequenzen.

Für eine lokale Entwicklungsmaschine kann es jedoch im Vergleich zum Problem unverhältnismäßig erscheinen, Playbooks zu schreiben, die sicherstellen, dass PostgreSQL, Redis und RabbitMQ installiert und richtig konfiguriert sind.

Direktes Verwenden von Docker run

Die nächste technische Alternative zu Compose ist die Verwendung des rohen docker run-Befehls. Für einen einzelnen Dienst kann dies sauber und effizient sein. Ein Befehl startet einen Container, weist einen Port zu, und Sie sind fertig.

Die Komplexität steigt schnell, sobald weitere Dienste hinzugefügt werden. Portzuordnungen, benannte Volumes für persistente Daten, Umgebungsvariablen für Anmeldedaten, Neustart-Richtlinien und benutzerdefinierte Netzwerke müssen alle manuell angegeben werden. Sobald Dienste miteinander kommunizieren müssen, wird das Merken jedes einzelnen Flags fehleranfällig. Seien wir ehrlich, niemand kann sich diese langen Befehle merken!

Nach drei oder vier Containern kopieren viele Entwickler lange Befehle aus einem Dokument oder verpacken sie in ein Shell-Skript (war schon da, hab das gemacht). In diesem Stadium ähnelt das Skript einer handgeschriebenen und weniger lesbaren Version einer docker-compose.yml-Datei.

Warum Compose praktisch bleibt

Docker Compose wandelt diese langen, imperativen docker run-Aufrufe in eine strukturierte, deklarative Konfiguration um. Alle Dienste sind in einer einzigen Datei definiert und die Vernetzung zwischen Containern wird automatisch erstellt. Die Vernetzung, selbst im Fall von Compose, kann komplex werden, aber das ist eine Geschichte für einen anderen Tag, denn größtenteils werden Sie sich nicht damit beschäftigen müssen.

Der eigentliche Vorteil von Compose liegt nicht nur in der Exklusivität, sondern in der Klarheit und Reproduzierbarkeit, die es bietet. Für die meisten lokalen Entwicklungsumgebungen bietet eine Compose-Datei ein praktisches Gleichgewicht zwischen Einfachheit und Kontrolle, ohne dass Ihr Basissystem dauerhaft um jedes Projekt herum gestaltet werden muss, mit dem Sie experimentieren.


Warum containerisierte Entwicklungsumgebungen so viel Zeit sparen

Die Verwendung einer Docker-Compose-Datei zur Erstellung einer lokalen Entwicklungsumgebung spart die Zeit, die man sonst für die Installation, Konfiguration und Fehlerbehebung nativer Dienste aufwenden würde. Wenn ein Projekt abgeschlossen ist, kann man das Projektverzeichnis löschen, die Container herunterfahren, und es bleibt keine Spur auf dem System zurück. Ich behaupte nicht, dass Compose objektiv die beste Option in allen Fällen ist. In Bezug auf die einfache Einrichtung und Zeiteffizienz ist es jedoch schwer zu übertreffen, insbesondere für Entwickler, die gerade erst anfangen, ihre lokalen Umgebungen zu organisieren. Präferenzen entwickeln sich in der Regel weiter, wenn die Erfahrung zunimmt und die Anforderungen spezialisierter werden.

Im Laufe der Zeit hat sich mein eigener Arbeitsablauf verändert. Ich habe mich allmählich in Richtung Werkzeuge wie asdf und Podman Pods für bestimmte Projekte bewegt, insbesondere dort, wo ich eine daemonlose und stärker in das Hostsystem integrierte Erfahrung bevorzuge. Die Grundidee bleibt jedoch dieselbe: Verwandte Dienste gruppieren, sie vom Basissystem isolieren und den gesamten Stack reproduzierbar machen.

Verwandte Artikel