Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Grundlagen Schwierigkeit: Profi

Den ganzen Docker-Stack sicher aktuell halten

Update-Disziplin für deinen Docker-Stack: Versionen pinnen, mit Diun über Updates informiert werden, sicher einspielen, aufräumen – statt blindem Auto-Update.

· 13 Min. Lesezeit ·Dauer: ca. 40 Minuten
Inhaltsverzeichnis

Ein selbstgehosteter Stack ist nie „fertig". Container-Images bekommen Sicherheitsupdates, Apps neue Funktionen – und wer nicht nachzieht, läuft irgendwann verwundbare Software. Dieses Rezept macht aus „ich sollte mal updaten" eine verlässliche Routine: informiert, kontrolliert und mit Netz.

Was bauen wir?

Kein Werkzeug, sondern eine Update-Disziplin für deinen kompletten Docker-Stack. Am Ende hast du: gepinnte Versionen (nie wieder latest), einen Update-Melder (Diun), der dir sagt, wann etwas Neues da ist, eine sichere Einspiel-Routine mit Backup davor und Rollback-Plan – und einen Aufräum-Rhythmus, damit dir alte Images nicht die Platte vollmüllen. Getestet mit Diun v4.33.0 unter Docker 29 / Compose v5.3.

Die Leitidee ist bewusst nicht „alles automatisch aktualisieren". Bei zustandsbehafteten Apps (Datenbanken, Nextcloud, Immich) kann ein unbeaufsichtigtes Update mitten in der Nacht eine Migration auslösen, die schiefgeht – und niemand ist da. Der sichere Weg ist: pinnen → benachrichtigen lassen → bewusst einspielen → prüfen. Blindes Auto-Update behandeln wir am Ende ehrlich (Schritt 6): Es hat seinen Platz, aber einen kleinen.

Voraussetzungen

  • Grundwissen zu Docker Compose – Tags, Volumes und compose.yaml solltest du lesen können.
  • Ein laufender Stack, den du pflegen willst (z. B. die Apps hinter deinem Reverse Proxy).
  • Eine funktionierende Backup-Strategie mit Restic – sie ist das Sicherheitsnetz, das Updates erst entspannt macht.
🍳 Empfehlung Anzeige

VPS 2000 G12

8 vCore · 16 GB RAM · 512 GB NVMe

ab 19,24 €/Monat

Ein gepflegter Multi-App-Stack samt Datenbanken fühlt sich auf dem VPS 2000 spürbar wohler.

Zu netcup →

💶 5 € Gutschein für netcup-Neukunden:36nc17844976032 (nur Neukunden, keine Domains)

Schritt für Schritt

Schritt 1: Nie wieder latest – Versionen bewusst pinnen

Der häufigste Fehler steht in unzähligen Anleitungen: image: nextcloud:latest. Das Problem: latest ist ein bewegliches Ziel. Ein docker compose pull kann dir jederzeit ungefragt eine neue Hauptversion samt Breaking Changes ziehen – du weißt nie, was du gerade fährst, und ein Rollback ist kaum möglich.

Pinne stattdessen jeden Dienst auf einen festen Tag. Zwei sinnvolle Stufen:

  • Major-Tag (postgres:18, nextcloud:34-apache): bekommt automatisch Patch- und Minor-Updates derselben Hauptversion beim nächsten pull, aber nie einen Major-Sprung. Guter Kompromiss für die meisten Dienste.
  • Exakte Version (vaultwarden/server:1.37.1): volle Kontrolle, nichts ändert sich ohne dein Zutun. Ideal für heikle, zustandsbehaftete Apps.

Was du gerade fährst, zeigt dir Compose pro Projekt:

Terminal
cd ~/DEINE_APP && docker compose images
Ausgabe
CONTAINER           REPOSITORY          TAG                 PLATFORM            IMAGE ID            SIZE                CREATED
diun-demo-whoami    traefik/whoami      v1.11.0             linux/amd64         200689790a0a        3.04MB              17 months ago

Steht in der TAG-Spalte irgendwo latest, ist das dein erster Kandidat zum Pinnen. Trag den konkreten Tag in die compose.yaml ein – das ist die Grundlage für alles Weitere.

Welche Version aktuell stabil ist, schlägst du an der Quelle nach, nicht im Bauchgefühl: die Tags-Übersicht auf Docker Hub (bzw. GHCR) des Images oder die Release-Notes auf GitHub. Für offizielle Images geht das auch per API:

Terminal
curl -s "https://hub.docker.com/v2/repositories/library/postgres/tags/?page_size=20" | grep -oE '"name":"[0-9.]+"'

Nimm die höchste stabile Version derselben Reihe, die du fahren willst – Vorabversionen (rc, beta) bleiben außen vor.

Schritt 2: Sehen, was veraltet ist – der Update-Melder Diun

Pinnen heißt: Updates kommen nicht mehr von allein. Also brauchst du jemanden, der dir Bescheid gibt, wenn ein neues Image da ist. Genau das macht Diun (Docker Image Update Notifier) – es aktualisiert nichts, es beobachtet nur und benachrichtigt. Leg ihm einen eigenen Ordner an:

Terminal
mkdir -p ~/diun && cd ~/diun

Die compose.yaml:

YAML
services:
  diun:
    image: crazymax/diun:4.33.0
    command: serve
    environment:
      - "TZ=Europe/Berlin"
      - "DIUN_WATCH_SCHEDULE=0 8 * * *"          # täglich 8 Uhr
      - "DIUN_PROVIDERS_DOCKER=true"
      - "DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=false"
    volumes:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped
  • DIUN_PROVIDERS_DOCKER=true lässt Diun deine laufenden Container über den Docker-Socket entdecken.
  • WATCHBYDEFAULT=false heißt: Diun beobachtet nur Container, die du ausdrücklich markierst – so bekommst du keine Flut zu Dingen, die dich nicht interessieren.
  • DIUN_WATCH_SCHEDULE ist ein Cron-Ausdruck; einmal täglich reicht völlig und schont die Registry-Rate-Limits.

Der Docker-Socket ist kein harmloser Lese-Zugriff

Wir mounten den Socket hier mit :ro, und das klingt beruhigend – ist es aber nur halb. Das :ro verhindert bloß, dass die Datei docker.sock überschrieben wird; über die Docker-API dahinter lässt sich trotzdem alles anstellen: Container mit --privileged starten, das Host-Dateisystem einhängen, kurz Root auf dem Host. Wer Zugriff auf den Socket hat, ist praktisch root-äquivalent – unabhängig vom :ro. Für einen reinen Melder wie Diun ist das ein bewusst eingegangenes Risiko; wer es entschärfen will, hängt Diun nicht direkt an den Socket, sondern an einen vorgeschalteten docker-socket-proxy, der nur die wenigen Endpunkte (Container auflisten, Images lesen) durchreicht, die Diun wirklich braucht.

Markiere die Container, die Diun im Auge behalten soll, per Label in deren compose.yaml:

YAML
    labels:
      - "diun.enable=true"
      - "diun.watch_repo=true"

diun.enable=true schaltet die Überwachung ein. diun.watch_repo=true lässt Diun das ganze Repository nach neueren Tags absuchen (z. B. ob zu deinem postgres:18 schon ein 19 existiert) – ohne dieses Label prüft Diun nur, ob dein exakter Tag ein neues Image bekommen hat. Starte Diun und sieh beim ersten Lauf zu:

Terminal
docker compose up -d && docker compose logs -f diun
Ausgabe
INF Found 1 image(s) to analyze provider=docker
INF New image found      image=docker.io/traefik/whoami:v1.11.0 provider=docker
INF New image found      image=docker.io/traefik/whoami:v1.12.0 provider=docker
INF New image found      image=docker.io/traefik/whoami:latest-arm64 provider=docker
INF Jobs completed       added=97 failed=0 skipped=0 unchanged=0 updated=0

Der erste Lauf ist nur die Bestandsaufnahme: Diun schreibt jeden gefundenen Tag als „New image found" in seine Datenbank (added). Dass für einen überwachten Container 97 Einträge herauskommen, liegt an watch_repo – Diun holt dann das Manifest für jeden Tag des Repositories, also auch für Architektur-Varianten wie latest-arm64 oder v1.10.0-armv7.

Zwei Dinge folgen daraus, die man leicht falsch erwartet:

  • Der erste Lauf benachrichtigt nicht. Die Option watch.firstCheckNotif steht per Default auf false, und das gilt pro Image-Tag. Alles, was Diun zum ersten Mal sieht, landet stillschweigend in der Datenbank. Gemeldet wird erst, wenn sich später etwas ändert.

  • watch_repo kostet Registry-Anfragen. 97 Manifest-Abfragen pro Lauf reißen das Docker-Hub-Limit ohne Login (100 Anfragen pro IPv4-Adresse bzw. IPv6-/64-Netz) fast alleine. Beim zweiten Lauf desselben Tages lief unser Test genau dort hinein – StatusCode: 429 und failed=36 im Log. Wie viel Budget noch übrig ist – und über welches Zeitfenster die Registry gerade rechnet –, sagt sie dir selbst:

    Terminal
    TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | cut -d'"' -f4)
    curl -sI -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i "^ratelimit"
    Ausgabe
    ratelimit-limit: 100;w=3600
    ratelimit-remaining: 98;w=3600

    w ist die Fensterbreite in Sekunden. Docker dokumentiert ein Sechs-Stunden-Fenster; unser Test-VPS bekam im August 2026 durchweg w=3600, also eine Stunde. Verlass dich deshalb auf den Header statt auf eine gemerkte Zahl – und beachte, dass diese Abfrage selbst eine Anfrage kostet. Grenz die Tags außerdem auf das ein, was dich wirklich interessiert – echte Release-Tags:

YAML
    labels:
      - "diun.enable=true"
      - "diun.watch_repo=true"
      - "diun.include_tags=^v1\\.1[12]\\.\\d+$$"
      - "diun.sort_tags=semver"

include_tags ist ein regulärer Ausdruck. Zwei Fallstricke stecken in der Schreibweise: Die Backslashes müssen im YAML doppelt stehen, und das $ am Ende wird als $$ geschrieben – sonst versucht Compose, eine Umgebungsvariable einzusetzen. Mit diesem Label bleiben von den 97 Einträgen genau die beiden echten Release-Tags übrig:

Ausgabe
INF Found 1 image(s) to analyze provider=docker
INF New image found      image=docker.io/traefik/whoami:v1.11.0 provider=docker
INF New image found      image=docker.io/traefik/whoami:v1.12.0 provider=docker
INF Jobs completed       added=2 failed=0 skipped=0 unchanged=0 updated=0

Damit die Meldung nicht nur im Log steht, hängst du eine Benachrichtigung dran. Diun kann E-Mail, Telegram, ntfy, Gotify und viele weitere. Für Telegram – den Bot hast du vielleicht schon im Uptime-Kuma-Tutorial eingerichtet – reichen zwei Zeilen im environment-Block:

YAML
      - "DIUN_NOTIF_TELEGRAM_TOKEN=DEIN_BOT_TOKEN"
      - "DIUN_NOTIF_TELEGRAM_CHATIDS=DEINE_CHAT_ID"

Alle weiteren Kanäle stehen in der Diun-Dokumentation. So sieht die Meldung dann aus – hier über ntfy im Browser, sobald zu einem deiner gepinnten Images eine neuere Version erscheint:

Die ntfy-Weboberfläche zeigt eine Diun-Benachrichtigung mit dem Titel „docker.io/traefik/whoami:v1.12.0 is available" und dem Hinweistext, dass der Tag über den Docker-Provider auf docker.io verfügbar ist
Diuns Update-Meldung: zum gepinnten whoami v1.11.0 ist v1.12.0 erschienen

Die Benachrichtigung testen, ohne wochenlang zu warten

Weil der erste Lauf absichtlich stumm bleibt, weiß man nach dem Einrichten nicht, ob der Meldeweg überhaupt funktioniert – im schlimmsten Fall merkst du das erst, wenn du eine wichtige Meldung verpasst hast. Setz deshalb einmalig DIUN_WATCH_FIRSTCHECKNOTIF=true in Diuns environment, lösche ./data und starte neu: Dann schickt Diun auch für die Bestandsaufnahme Benachrichtigungen – genau eine pro gefundenem Tag. Kanal geprüft, Variable danach wieder entfernen (sonst rauscht dir der nächste watch_repo-Lauf den Posteingang voll).

Schritt 3: Ein Update sicher einspielen (die Routine)

Diun meldet ein Update – jetzt kommt die eigentliche Disziplin. Nie einfach blind ziehen. Die feste Reihenfolge für jeden Dienst:

1. Release-Notes lesen. Bei einem Major-Sprung (z. B. Nextcloud 34 → 35) ist das Pflicht: Gibt es Breaking Changes, Migrationsschritte, entfernte Optionen? Zwei Minuten hier sparen Stunden Fehlersuche.

2. Vorher sichern. Ein Update ist der klassische Moment, an dem etwas kaputtgeht. Mach ein Restic-Backup der Daten (bei großen Sprüngen zusätzlich einen netcup-Snapshot). Dann ist ein Fehlschlag nur ein Rücksetzer, kein Datenverlust.

3. Tag anheben und ziehen. Setz den neuen Tag in der compose.yaml und hol das Image:

Terminal
docker compose pull
Ausgabe
 Image traefik/whoami:v1.12.0 Pulled

4. Neu starten. Compose ersetzt nur den betroffenen Container, die Volumes bleiben:

Terminal
docker compose up -d
Ausgabe
 Container diun-demo-whoami Recreated
 Container diun-demo-whoami Started

5. Prüfen. Läuft der Container sauber (docker compose ps, docker compose logs), und tut die App noch, was sie soll? Zustandsbehaftete Apps führen jetzt evtl. eine Datenbank-Migration aus – wirf einen Blick ins Log, bis sie durch ist.

6. Rollback im Kopf haben. Geht etwas schief, setzt du den Tag in der compose.yaml zurück auf die alte Version und machst erneut docker compose up -d. Weil deine Daten im Volume liegen (nicht im Container), ist das bei den meisten Apps ein sauberer Rücksprung. Nur wenn die neue Version die Datenbank schon migriert hat, hilft kein Tag-Rollback mehr – dann brauchst du das Backup aus Schritt 2. Genau dafür ist es da.

Eine App nach der anderen

Aktualisiere nicht den ganzen Stack auf einmal. Geh Dienst für Dienst vor und prüfe jeweils, ob alles läuft, bevor du zum nächsten gehst. Bricht dann etwas, weißt du sofort, welches Update schuld war.

Schritt 4: Die Basis nicht vergessen – Host, Engine, versteckte Images

Deine Apps sind nur die halbe Miete. Genauso wichtig:

  • Betriebssystem & Docker-Engine. Die OS- und Sicherheitspakete von Debian spielst du am besten automatisch mit unattended-upgrades ein. Die Docker-Engine bleibt dabei bewusst außen vor: unattended-upgrades zieht per Default nur die Debian-Quellen (origin=Debian), nicht das Docker-APT-Repo (origin=Docker, download.docker.com). Die Engine wird also nicht im Hintergrund aktualisiert und startet dir nachts nicht unerwartet alle Container neu – denn ein Engine-Update reißt laufende Container kurz mit. Diesen Moment bestimmst du besser selbst und aktualisierst die Engine gezielt von Hand, wenn es passt:

    Terminal
    sudo apt update && sudo apt upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    Prüfe danach mit docker compose ps, dass alle Stacks wieder oben sind.

    Ist dir ein kurzer Container-Neustart in der Nacht egal, kannst du die Engine auch mit unattended-upgrades mitziehen lassen: Erlaube in /etc/apt/apt.conf.d/50unattended-upgrades das Docker-Repo als zusätzliches Origins-Pattern. Wichtig ist archive= – das Docker-Repo setzt kein Codename-Feld, das oft empfohlene codename=${distro_codename} würde ins Leere laufen und Docker bliebe trotz Eintrag außen vor:

    Ausgabe
    "origin=Docker,archive=${distro_codename}";
  • Versteckte Images. In vielen Stacks stecken Datenbanken, Caches und Hilfsdienste (postgres, redis, mariadb, gotenberg …), an die niemand denkt. Diun überwacht sie automatisch mit, sobald der jeweilige Container das diun.enable-Label trägt – gib es also allen langlebigen Diensten, nicht nur der sichtbaren Haupt-App.

  • Erweiterungen deines Reverse Proxys. Auch gepinnte Plugin-Versionen (etwa der CrowdSec-Bouncer) oder Traefik selbst wollen bewusst nachgezogen werden.

Schritt 5: Aufräumen – Speicher zurückholen

Jedes Update lässt das alte Image liegen. Nach ein paar Monaten summiert sich das. Was du verbrauchst, zeigt:

Terminal
docker system df
Ausgabe
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          26        4         14.75GB   14.16GB (95%)
Containers      4         4         45.06kB   0B (0%)
Local Volumes   0         0         0B        0B
Build Cache     36        0         828.2MB   828.2MB

Ungenutzte, unbenannte („dangling") Images entfernt der sichere Standardbefehl:

Terminal
docker image prune

Achtung: Das entfernt nur dangling Images. Eine alte, noch getaggte Version (wie whoami:v1.11.0 nach dem Update auf v1.12.0) gilt nicht als dangling und bleibt liegen. Die holst du dir gezielt zurück:

Terminal
docker rmi traefik/whoami:v1.11.0
Ausgabe
Untagged: traefik/whoami:v1.11.0
Deleted: sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab

Wer radikaler aufräumen will, nimmt docker image prune -a (entfernt alle Images, die gerade kein laufender Container nutzt). Das ist mächtig, zieht aber beim nächsten Start alles neu – setz es bewusst ein, nicht im Cron.

Das sichere docker image prune -f (nur dangling) kannst du dagegen bedenkenlos regelmäßig laufen lassen – etwa wöchentlich per systemd-Timer oder Cron. So bleibt zwischen deinen Update-Terminals gar nicht erst Müll liegen, und du gewinnst den Speicher automatisch zurück.

Schritt 6: Auto-Update – wann es okay ist (und wann nicht)

Bleibt die Frage: Warum nicht alles vollautomatisch? Das bekannteste Werkzeug dafür ist Watchtower – das zieht neue Images und startet Container von selbst neu. Zwei Haken:

  • Der Upstream ist verwaist. Das offizielle containrrr/watchtower hat seit v1.7.1 (11.11.2023) kein Release mehr bekommen – und das Repository wurde Ende 2025 (17.12.2025) archiviert, also endgültig eingemottet. Wer es nutzt, greift besser zu einem gepflegten Fork wie nickfedor/watchtower.
  • Auto-Update ist für zustandsbehaftete Apps gefährlich. Eine Datenbank oder Nextcloud nachts unbeaufsichtigt über einen Major-Sprung zu heben, ist das Gegenteil von „sicher".

Sinnvoll ist Auto-Update höchstens für unkritische, zustandslose Dienste – und selbst dann lieber im Monitor-Modus (WATCHTOWER_MONITOR_ONLY=true), der nur meldet statt aktualisiert. Für alles andere schlägt die bewusste Routine aus Schritt 3 jedes Automatik-Werkzeug. Genau deshalb ist Diun (melden) + manuelles Update hier die Empfehlung, nicht Watchtower (handeln).

Sichere Automatisierung für Git-Nutzer: Renovate

Wenn deine compose.yaml-Dateien in einem Git-Repo liegen, gibt es einen dritten Weg, der Automatik und Kontrolle verbindet: Renovate (oder Dependabot) erkennt die gepinnten image:-Tags und öffnet automatisch einen Pull Request, der den Tag anhebt – mitsamt Link zu den Release-Notes. Das Update passiert also nicht heimlich am Server, sondern als Änderung, die du prüfst und mergst, bevor du sie mit docker compose up -d ausrollst. So bleibt der sichere „bewusst einspielen"-Schritt erhalten, nur das lästige Nachschauen entfällt.

Wenn es nicht funktioniert

docker compose pull zieht kein neues Image, obwohl es eine neue Version gibt. Dein Tag zeigt auf eine feste Version (:1.37.1) oder auf einen Major-Tag (:18), unter dem es nur ein neues Major gäbe. pull holt nur, was derselbe Tag inzwischen zeigt. Für einen Versionssprung musst du den Tag in der compose.yaml selbst anheben.

Die App startet nach dem Update nicht mehr oder wirft Datenbank-Fehler. Meist ein Breaking Change oder eine fehlgeschlagene Migration. Prüfe docker compose logs, gleiche mit den Release-Notes ab. Tag auf die alte Version zurücksetzen und up -d; hat die neue Version die Daten schon migriert, das Backup aus Schritt 3 einspielen.

Diun meldet nichts, obwohl Updates existieren. Prüfe, dass die Container das Label diun.enable=true tragen und Diun den Socket lesen kann (DIUN_PROVIDERS_DOCKER=true, Socket gemountet). Für neuere Versions-Tags braucht der Container zusätzlich diun.watch_repo=true – ohne das sieht Diun nur Änderungen am exakt gepinnten Tag. Und: Was Diun beim ersten Mal sieht, wird nur in die Datenbank geschrieben, nicht gemeldet (watch.firstCheckNotif ist false) – zum Testen des Meldewegs einmalig DIUN_WATCH_FIRSTCHECKNOTIF=true setzen.

Die Festplatte läuft voll, obwohl du regelmäßig docker image prune machst. docker image prune räumt nur dangling Images ab, nicht die alten getaggten Versionen. Entferne sie gezielt mit docker rmi <image>:<tag> oder – mit Bedacht – docker image prune -a. Auch der Build-Cache (docker builder prune) kann wachsen.

Beim Ziehen kommt toomanyrequests / ein Docker-Hub-Rate-Limit. diun.watch_repo=true auf vielen Images fragt viele Tags ab. Setz das Watch-Intervall seltener (z. B. einmal täglich) und grenze mit diun.include_tags auf relevante Versionen ein, statt das ganze Repo abzuklopfen.

Wartung & Backups

  • Der Rhythmus. Diun meldet → du liest die Release-Notes → Backup → Update → prüfen. In der Praxis ist das einmal im Monat ein überschaubarer Termin; kritische Sicherheitslücken ziehst du sofort nach. Ehrlich: Ganz ohne Handarbeit geht sicheres Selfhosting nicht – aber 15 Minuten im Monat sind der Deal.
  • Schnelldreher zuerst. Manche Projekte veröffentlichen häufig und mit Breaking Changes – Immich, mailcow, CrowdSec, Grafana und Nextcloud. Die schaust du dir zuerst und öfter an, ruhige Kandidaten (Datenbanken, Redis) seltener.
  • Backups sind der Kern dieses Rezepts. Ein Update ohne Backup ist ein Vabanquespiel; mit Restic im Rücken wird jedes Update entspannt. Vor großen Sprüngen zusätzlich einen Snapshot – der holt den ganzen Server in einem Rutsch zurück.
  • Versioniere deine compose.yaml-Dateien. Wer seine Compose-Dateien in einem Git-Repo hält, kann nicht nur Daten, sondern auch die Konfiguration auf jeden früheren Stand zurückrollen – und sieht in der Historie genau, welcher Tag wann angehoben wurde.
  • Halte auch die Werkzeuge aktuell. Diun selbst und deine Reverse-Proxy-Plugins gehören mit in die Update-Runde – sonst pflegt sich der Pfleger nicht.

Zuletzt aktualisiert: 11.08.2026

Feedback per E-Mail: feedback@serverkueche.de

Das könnte dir auch schmecken