Lokales CI/CD für den Raspberry Pi: Automatisierung von Docker-Compose-Deployments
Teil 3 von 7 der Reihe Homelab auf dem Raspberry Pi

Inhalt
Eine lokale CI/CD-Pipeline kann Änderungen prüfen und Docker-Compose-Dienste auf einem Raspberry Pi bereitstellen. Dadurch entfällt bei Routineupdates eine zusätzliche SSH-Sitzung. Git enthält die Bereitstellungsdefinition; ein lokaler Ausführungsdienst übernimmt einen freigegebenen Stand. Sicherungen, Überwachung und Wiederherstellungsverfahren bleiben erforderlich.
1. Einen reproduzierbaren Stand festlegen
CI bezeichnet automatisierte Integrationsprüfungen, CD die Auslieferung oder Bereitstellung. Ein Webhook startet lediglich einen Auftrag. Bei GitOps beschreibt Git den gewünschten Zustand; ein automatisierter Prozess gleicht die laufenden Dienste mit dieser Beschreibung ab.
- Konfiguration: Compose beschreibt Dienste, Netzwerke und eingebundene Volumes. Speicherinhalte und Datenbankeinträge stehen nicht in der YAML-Datei. Geheimnisse gehören nicht in ein gewöhnliches Git-Repository.
- Eindeutige Version: Git-Commit und Image-Digests halten den freigegebenen Stand fest. Ein veränderliches Tag wie
:latestreicht dafür nicht aus. - Wiederherstellung: Git erleichtert die Nachverfolgung, garantiert aber keine unveränderliche Historie. Eine ältere Konfiguration macht Datenbankmigrationen oder gelöschte Dateien nicht rückgängig. Dafür sind Sicherungen und ein erprobter Wiederherstellungsweg nötig.

2. Webhook prüfen und Auftrag ausführen
Ein Listener wie adnanh/webhook empfängt das Repository-Ereignis. Bei GitHub wird X-Hub-Signature-256 anhand des unveränderten Anfrageinhalts und des Webhook-Geheimnisses geprüft. Der Vergleich muss zeitkonstant erfolgen. HMAC bestätigt den signierten Inhalt; zusätzlich müssen Ereignis, Repository und erlaubter Branch zur Deployment-Regel passen.
- Ungültige oder nicht erlaubte Anfragen ablehnen. Zustellungs-IDs protokollieren, um doppelte Zustellungen zu erkennen und erneute Ausführungen zu verhindern.
- Einen geprüften Auftrag ohne Docker-Zugriff in eine Warteschlange stellen. Beliebige Nutzlastfelder dürfen nicht zu Shell-Befehlen werden.
- Ein getrennter Ausführungsdienst lädt den freigegebenen Commit in ein kontrolliertes Release-Verzeichnis. Eine Sperre verhindert parallele Änderungen am gleichen Stack.
- Die vollständige Compose-Konfiguration prüfen, Images ausdrücklich herunterladen oder bauen und anschließend den Stand starten.
docker compose up -d --remove-orphans bildet allein noch keine vollständige Pipeline. Geänderte Images oder Konfigurationen führen zur Neuerstellung vorhandener Container. Ein neuer Inhalt hinter einem unveränderten entfernten Tag wird dadurch nicht zuverlässig abgerufen. Dafür sind eine festgelegte Pull-Strategie oder ein eigener Pull-Schritt nötig. Projektname und verwendete Compose-Dateien müssen zusammenpassen.
3. Funktionsprüfung und Fehlerbehandlung
docker compose up --wait --wait-timeout 120 wartet auf laufende beziehungsweise gesunde Dienste und aktiviert den Hintergrundbetrieb. Die Grenze von 120 Sekunden ist ein Beispiel. Ohne Health-Check wird bei einem Dienst lediglich der laufende Zustand geprüft.
Ein Docker-HEALTHCHECK meldet den Zustand nach den festgelegten Intervallen und Fehlversuchen. unhealthy löst bei gewöhnlichem Docker Compose allein weder Neustart noch Rollback aus. Dafür braucht der Ausführungsdienst eine ausdrückliche Fehlerbehandlung. Anwendungsprüfungen und aussagekräftige Protokolle bleiben nötig: Ein laufender Prozess belegt noch keinen funktionierenden Login oder Datenbankzugriff.
Lokale ARM-Builds können Cross-Compilation vermeiden, sofern Basisimages und Abhängigkeiten die Zielarchitektur unterstützen. Dafür beanspruchen Builds Rechenleistung, Arbeitsspeicher und Speicherplatz auf dem Pi.
4. Netzwerkzugang und Deployment-Rechte trennen
Der erreichbare Listener sollte ohne Root-Rechte und ohne Docker-Socket-Zugriff laufen. Die Gruppe docker verleiht bei einem gewöhnlichen Docker-Daemon mit Root-Rechten Befugnisse auf Root-Niveau. Ein schreibgeschützter Socket-Proxy erlaubt Kontrollen, aber keine Bereitstellung mit Containeränderungen.
Ein kleiner privilegierter Ausführungsdienst akzeptiert ausschließlich geprüfte Aufträge für feste Repositories und Ziele. Compose-Dateien können weitreichende Mounts oder Privilegien verlangen; deshalb müssen auch Konfiguration und Code vertrauenswürdig sein. Ein Management-VLAN mit Firewallregeln begrenzt Zugriffswege. Gegen kompromittierte Geräte in erlaubten Netzen besteht dadurch keine Schutzgarantie.
5. GitHub Actions und beschränkte SSH-Schlüssel
Ein GitHub-gehosteter Runner kann den Pi über ein authentifiziertes privates Netz oder VPN per SSH erreichen. Ein öffentlich freigegebener SSH-Port ist dafür nicht zwingend nötig. Ein selbst gehosteter Runner fordert Aufträge bei GitHub an. Die aktuelle Referenz nennt Linux ARM64, führt ARM32 aber nicht mehr auf. Betriebssystem, Architektur und verwendete Actions müssen vor der Installation zusammenpassen.
Für die Verbindung zu GitHub benötigt der Runner ausgehendes HTTPS auf Port 443. Einzelne Jobs brauchen gegebenenfalls weitere Git-, Registry- oder Anwendungsverbindungen. Für die Auftragsabholung ist kein eingehender Port erforderlich. Auch ein privates Repository macht beliebigen Workflow-Code nicht vertrauenswürdig. Ungeprüfter Pull-Request-Code darf keine Deployment-Zugangsdaten oder produktiven Docker-Rechte erhalten.
Ein eigener Deployment-Schlüssel kann in authorized_keys nach folgendem Muster beschränkt werden. AAAA… steht als Platzhalter für den vollständigen öffentlichen Schlüssel:
restrict,command="/usr/local/bin/deploy-stack" ssh-ed25519 AAAA… ci-deploy
command= erzwingt den hinterlegten Befehl. restrict deaktiviert Weiterleitungen, PTY-Zuteilung und ~/.ssh/rc. Das Skript muss unzulässige Eingaben einschließlich SSH_ORIGINAL_COMMAND abweisen; Abhängigkeiten und Konfiguration dürfen durch den Aufrufer nicht veränderbar sein. Ein gestohlener Schlüssel kann den erlaubten Vorgang weiterhin wiederholt auslösen. Der feste Befehl allein bildet keine vollständige Sicherheitsgrenze.
6. Image-Updates planen
Das ursprüngliche Repository containrrr/watchtower wurde am 17. Dezember 2025 archiviert. Vor einem Wechsel zu Community-Forks wie nickfedor/watchtower sind Version und Docker-API-Kompatibilität gesondert zu prüfen. Die Image-Überwachung ersetzt Container, testet aber keine Anwendungskompatibilität.
Die verlinkte Referenz zu Watchtower v1.14.3 nennt 86.400 Sekunden als Standardintervall und sechs Felder für WATCHTOWER_SCHEDULE. 0 0 4 * * * steht für 4:00 Uhr. TZ=Europe/Warsaw legt die gewünschte lokale Zeitzone ausdrücklich fest; dokumentierter Standard ist UTC. Zeitplan und WATCHTOWER_POLL_INTERVAL sind Alternativen.
WATCHTOWER_LABEL_ENABLE=true begrenzt Updates auf Container mit com.centurylinklabs.watchtower.enable=true. WATCHTOWER_CLEANUP=true entfernt alte Images nach Updates, verwaltet aber nicht sämtliche Logs oder Volume-Daten. Dabei kann auch ein lokal verfügbares Rollback-Image entfallen. Pro Dienst sollte ein Update-Verfahren zuständig sein, damit GitOps und Watchtower nicht unabhängig voneinander widersprüchliche Stände erzeugen.
Fragen und Antworten
Was passiert mit unveränderten Containern und Volumes?
Ohne erzwungene Neuerstellung können unveränderte Container weiterlaufen. Compose erhält beim Ersetzen eingebundene Volumes; Daten ausschließlich im beschreibbaren Container-Dateisystem gehen dagegen mit dem Container verloren. Anonyme Volumes und abweichende Mount-Konfigurationen verdienen eine gesonderte Prüfung. Eine Volume-Einbindung ersetzt keine Sicherung.
--remove-orphans entfernt Container desselben Compose-Projekts, deren Dienste in der verwendeten Konfiguration fehlen. Deshalb sind ein eindeutiger, stabiler Projektname über name: oder -p und die vollständige Dateiauswahl wichtig. Ein geteilter Projektname kann fremde Dienste gefährden; ein geänderter Name kann alte Container zurücklassen.
Quellen
- Docker Compose documentation
- GitHub Docs: Self-hosted runners reference
- GitHub Docs: Adding self-hosted runners
- OpenBSD manual: sshd(8), AUTHORIZED_KEYS FILE FORMAT
- Docker: Linux post-installation steps for Docker Engine
- Watchtower (nicholas-fedor): Arguments
- robfig/cron v1.2.0: CRON Expression Format
- Recreate, volumes, pull policy, wait and remove-orphans
- Health status, intervals and retries
- Restart policy operates on process exit
- HMAC SHA256 and constant-time comparison
- Event checks, queue, delivery IDs
- Private network access from hosted runners
- Original project archived December17,2025
Homelab auf dem Raspberry Pi
- Raspberry Pi 4 und 5: Vom SSD-Laufwerk booten über den Bootloader
- USV für Raspberry Pi: fünf Lösungen und Auswahlkriterien
- Lokales CI/CD für den Raspberry Pi: Automatisierung von Docker-Compose-Deployments
- Docker-Compose-Heimserver: Traefik, TLS und Update-Prüfung
- Jellyfin auf Raspberry Pi 5: Direct Play und Grenzen der Transkodierung
- Raspberry Pi als gehärtetes WireGuard-VPN-Gateway mit Split-Tunneling
- Raspberry Pi mit Prometheus und Grafana überwachen