Einführung: Das ist Docker-Compose und so installierst du es auf dem Raspberry Pi!

Als Video ansehen
Bereitgestellt über YouTube

Einführung: Das ist Docker-Compose und so installierst du es auf dem Raspberry Pi!

Wer sich mit Docker beschäftigt, wird recht schnell auf Docker-Compose stoßen. Doch was ist Docker-Compose? Worin unterscheidet es sich zu Docker? Brauche ich das, oder reicht mir das „normale“ Docker? Dieser Beitrag soll alle diese Fragen beantworten und dir helfen, einen einfachen Webserver mithilfe von Docker-Compose zum laufen zu bringen.

Welche Probleme entstehen mit plain Docker?

In unserem ersten Einstiegs-Teil für Docker auf dem Raspberry Pi haben wir uns die generelle Funktionsweise angeschaut und dies auch in der Praxis mit einem Webserver demonstriert. Für einen einzelnen Container ist das ausreichend. Doch im Alltag reicht das schnell nicht mehr aus: Nahezu jede Webanwendung benötigt eine Datenbank. Teils kommen noch weitere Komponenten dazu, etwa Cronjobs. Als Folge daraus hat man für eine einzelne Anwendung oft mindestens zwei Container.

Und wir müssen uns zunehmend um mehrere Dinge kümmern: Eine Datenbank muss beispielsweise im gleichen Netzwerk wie der Anwendungsserver liegen. Es muss also ein Netzwerk erstellt und beiden Containern zugewiesen werden. Ebenfalls alltäglich sind Volumes. Selbst für das simple Beispiel des Webservers aus dem vorherigen Teil haben wir eines gebraucht. Das alles lässt sich mit Docker-Befehlen lösen: docker network create, docker volume create und so weiter. Aus Erfahrung möchte ich euch aber davon abraten: Ihr versinkt schnell in einer Vielzahl immer länger werdender Docker-Befehle, die oft nicht sauber dokumentiert sind und später aber wieder gebraucht werden.

Wie löst Docker-Compose diese Probleme?

Docker-Compose führt die gleichnamige Yaml-Datei ein: YAML ist ein Format, um Daten in Form von Schlüssel-Wert Paaren darzustellen. In der docker-compose.yml legen wir unsere Container mit sämtlichen Eigenschaften fest: Vom Image über Volumes bis hin zu Ressourcenbegrenzungen und Netzwerken. Mithilfe von Docker-Compose genügen wenige einfache Befehle, um die darin definierten Container verwalten zu können.

Das hat mehrere Vorteile, die wohl Wichtigsten:

  • Alle Eigenschaften sind in einer Datei festgelegt, die wir z.B. mit Git in eine Versionsverwaltung ablegen können
  • Es gibt nur noch wenige, kurze Befehle und dadurch eine bessere Übersicht, da gerade bei komplexeren Containern docker run Befehle ziemlich lang und damit unübersichtlich werden können
  • Alle darin definierten Container lassen sich gleichzeitig starten/stoppen
  • In einer Docker-Compose Datei können mehrere Container definiert und miteinander verknüpft werden
  • Abhängigkeiten werden festgelegt und eingehalten, z.B. zuerst die Datenbank starten, danach die Anwendung
  • Volumes und Netzwerke lassen sich einfacher verwalten und automatisch erstellen
  • Compose erkennt Änderungen

Installation: Wie kann ich Docker-Compose auf dem Pi nutzen?

Nachtrag vom 31.07.2023: Mittlerweile installiert das offizielle Skript von Docker (wie dies genutzt wird, habe ich in diesem Beitrag gezeigt) automatisch die Compose-Erweiterung. Folgt ihr den Anweisungen aus diesem Beitrag, liefert docker compose version daher die Version zurück und belegt damit die Funktion der Erweiterung. Dies solltet ihr zuerst prüfen und die im Folgenden beschriebenen Schritte nur ausführen, wenn ihr dabei keine Version erhaltet (= Plugin ist noch nicht installiert)!

Intern baut Docker-Compose auf Docker auf – es ist keine eigene Container-Runtime wie Docker! Vereinfacht gesagt führt Docker-Compose im Hintergrund die Docker-Befehle aus und vereinfacht damit für uns. Als Voraussetzung müsst ihr daher zunächst Docker installiert haben, wie im Einstiegs-Beitrag erklärt. Anschließend lässt sich Docker-Compose zusätzlich installieren. Auf der Releases-Seite des Projektes sucht man sich die aktuellste stabile Version für armv7 heraus. Zum Zeitpunkt des Erstellens dieses Artikels war dies die Version 2.3.4. Man kann sie mit wget direkt auf den Pi herunterladen. Anschließend machen wir die Binärdatei ausführbar und verschieben sie in das von Linux dafür vorgesehen Verzeichnis, sodass Compose von überall aus genutzt werden kann:

wget wget https://github.com/docker/compose/releases/download/v2.3.4/docker-compose-linux-aarch64
chmod +x chmod +x docker-compose-linux-aarch64

mkdir -p ~/.docker/cli-plugins
mv docker-compose-linux-aarch64 ~/.docker/cli-plugins/docker-compose

Damit sind wir auch schon fertig und sollten den Befehl docker compose fortan nutzen können:

$ docker compose --version
Docker Compose version v2.3.4

Wie bei allen händisch installierten Programmen sollte man hier immer mal wieder schauen, ob Aktualisierungen zur Verfügung stehen und diese einspielen.

Ergänzung vom 26.03.2022: Die Installation wurde mit der empfohlenen Vorgehensweise für die neue Version 2 aktualisiert. Statt die ausführbare Datei in das Bin-Verzeichnis zu kopieren, erfolgt die Installation als Docker Plugin. Weitere Informationen über die Neuerungen in Version 2 findet ihr in diesem Beitrag. In V2 wird Compose mit Minus aufgerufen.

So startest du deinen ersten Container mit Docker-Compose

Als einfaches Beispiel wollen wir einen Nginx Webserver starten, wie im Beitrag zum Einstieg in Docker mit plain Docker bereits gezeigt. Auch wenn ihr mit Compose arbeiten möchtet kann ich euch diesen Beitrag empfehlen. Dort werden die Hintergründe detaillierter erklärt, beispielsweise zu den Images. Für Compose legen wir eine Yaml-Datei namens docker-compose.yml an. Wahlweise statt der yml Endung auch yaml, das ist eine umstrittene Geschmackssache und spielt letztendlich keine Rolle.

Zuerst geben wir die Version an. Das Compose-Format wurde mehrfach mit verschiedenen Versionen erweitert. Aktuell sind derzeit 2.4 und 3.8. Ich empfehle 2.4, da Version 3 nur im Cluster-Betrieb Vorteile bietet. Auf einer einzelnen Maschine bietet Version 2 dagegen sogar ein paar Vorteile.

version: "2.4"

Anschließend leiten wir eine Sektion services ein. Darin können beliebig viele Container definiert werden. Wichtig bei Yaml: Hierarchische Sektionen werden durch Einrücken gekennzeichnet. Tabs sind hier nicht zulässig, stattdessen Leerzeichen verwenden. Meist nutzt man zwei, es können aber auch mehr (z.B. 4) verwendet werden – allerdings konsistent.

version: "2.4"

services:
  webserver:
    image: nginx:1.21
    restart: always
    ports:
      - 80:80

Hier haben wir einen Container namens webserver mit dem Image nginx und Tag 1.21. Durch restart: always wird der Container automatisch gestartet, wenn der Pi bootet. Schlussendlich folgt ein Portmapping von Port 80 des Hosts auf den gleichen Port des Containers – dies entspricht -p 80:80 beim docker run Befehl.

Zum Starten genügt es, docker-compose wie folgt im gleichen Verzeichnis wie die docker-compose.yml aufzurufen:

docker compose up

Wie beim docker run Befehl wird zunächst das Image aus dem Internet geladen und anschließend der Container gestartet. Der Webserver ist anschließend über den Hostname oder die IP-Adresse des Pi im Browser erreichbar:

Allerdings haben wir hier nun das gleiche Problem wie bei docker run: Der Container läuft im Vordergrund und wird zusammen mit der SSH-Sitzung oder Konsole beendet. Auch hier gibt es den detached Modus mit dem Schalter -d:

docker compose up -d

Dadurch läuft der Container im Hintergrund. Logs lassen sich gebündelt von allen Containern wie folgt einsehen:

docker compose logs -f

Oder alternativ über docker logs mit Angabe des Containers. Welche Container Compose gestartet hat, sieht man mit dem Befehl ps:

$ docker compose ps
NAME                        COMMAND                  SERVICE             STATUS              PORTS
compose-nginx-webserver-1   "/docker-entrypoint.…"   webserver           running             0.0.0.0:80->80/tcp, :::80->80/tcp

Der generierte Name ist eine Kombination aus dem aktuellen Ordner und dem Dienstname. Stoppen kann man sämtliche Container über den Befehl down:

docker compose down

Eigene HTML-Seiten mit Volume ausliefern

Um eigene HTML-Seiten ausliefern zu können, erstellen wir einen www Ordner mit einer Indexseite:

mkdir www
echo '<h1>Testseite Docker-Compose</h1>' > /home/pi/www/index.html

und erweitern das Compose-File, sodass dieser lokale Ordner im Html-Wurzelverzeichnis des Webservers ausgeliefert wird:

version: "2.4"

services:
  webserver:
    image: nginx:1.21
    restart: always
    ports:
      - 80:80
    volumes:
      - ./www:/usr/share/nginx/html

Anschließend mit docker compose up den Container neu erzeugen und

Weiteren Ausbau

In diesem einfachen Einstiegsbeispiel haben wir nur einen einzigen Container. Sein volles Potenzial schöpft Docker-Compose aus, wenn mehrere Container für eine Anwendung nötig sind. Häufig sind dies etwa Datenbanken. Wie dies funktioniert und wie man Container in Abhängigkeit zueinander setzen kann, schauen wir uns im nächsten Teil an. Er wird dann an dieser Stelle verlinkt.

Leave a Reply