Zum Inhalt springen
myvpsguide

Container aktuell halten, ohne dass es nachts knallt

Warum :latest keine Version ist, wie Tags und Digests wirklich wirken, ein Update-Ablauf mit Rückweg und automatische Updates mit Augenmaß.

veröffentlicht 27.05.2026
4 min lesezeit
fortgeschritten

Container haben das Update-Problem nicht gelöst, sondern verschoben. Statt Paketen aktualisiert man Images und weil ein Image das ganze Betriebssystem des Dienstes enthält, bleibt eine alte Bibliothek darin so lange liegen, bis jemand das Image neu baut oder zieht.

:latest ist keine Version#

image: nextcloud:latest bedeutet nicht „die neueste stabile Fassung“. Es bedeutet: irgendein Stand, den der Herausgeber zuletzt so getaggt hat. Auf zwei Maschinen, die eine Woche auseinander gezogen haben, laufen damit zwei verschiedene Programme, bei identischer Compose-Datei.

Die Abstufung, von unbrauchbar bis reproduzierbar:

image: postgres:latest      # unbrauchbar im Betrieb
image: postgres:17          # bewegt sich innerhalb von 17.x
image: postgres:17.4        # fest genug für die meisten Fälle
image: postgres:17.4-alpine@sha256:9b1e...   # exakt dieses Image, für immer

Meine Praxis: Nebenversion festnageln (17.4), Digest nur dort, wo es wirklich exakt sein muss. Der Digest ist unbestechlich, macht aber jede Aktualisierung zu einer Änderung an zwei Stellen.

Was der Digest wirklich garantiert

Ein Tag kann neu gesetzt werden, derselbe Tag zeigt morgen auf ein anderes Image. Ein Digest ist der Hash des Manifests und ändert sich nie. Genau deshalb ist er in einer Lieferkette das einzige belastbare Merkmal: docker image inspect postgres:17.4 --format '{{index .RepoDigests 0}}'.

Ein Update-Ablauf mit Rückweg#

Der Punkt an einem Ablauf ist nicht, dass nichts schiefgeht, sondern dass man weiß, wie man zurückkommt.

cd /opt/appstack

# 1. Wissen, wo man herkommt. Diese Zeile ist der Rückweg.
docker compose config --images
docker compose images --format 'table {{.Service}}\t{{.Repository}}\t{{.Tag}}\t{{.ID}}'

# 2. Datenbestand sichern, bevor irgendetwas gezogen wird
docker compose exec -T db pg_dump -U app app | zstd -9 > /var/backups/app-vor-update.sql.zst

# 3. Neue Images holen, ohne sie zu starten
docker compose pull

# 4. Was ändert sich? Changelog des Projekts lesen — besonders auf
#    Migrationen, geänderte Umgebungsvariablen und Breaking Changes.

# 5. Umschalten
docker compose up -d

# 6. Hinsehen. Nicht wegklicken.
docker compose ps
docker compose logs -f --tail=100 app

Der Rückweg, falls Schritt 6 unschön aussieht:

# Tag in der compose.yaml auf die alte Version zurücksetzen
docker compose up -d
# Falls die neue Version das Schema migriert hat: Dump zurückspielen
zstd -d < /var/backups/app-vor-update.sql.zst | docker compose exec -T db psql -U app app
Der eigentliche Grund für Schritt 2

Ein Zurücksetzen des Images ist einfach. Ein Zurücksetzen einer Datenbankmigration ist es nicht. Viele Anwendungen migrieren beim Start automatisch und ohne Nachfrage; das alte Programm kann danach mit dem neuen Schema nichts mehr anfangen. Der Dump von Schritt 2 ist deshalb kein Formalismus, sondern die einzige echte Rückfahrkarte.

Was auf jeden Fall in eine Vorabprüfung gehört#

Bevor man ein Major-Update fährt, drei Punkte im Changelog suchen:

  1. Migrationen, ändert sich das Datenbankschema, und ist der Schritt umkehrbar?
  2. Entfernte Konfiguration, sind Umgebungsvariablen weggefallen oder umbenannt?
  3. Übersprungene Versionen, verlangt das Projekt, von 15 über 16 nach 17 zu gehen? Bei Datenbanken ist das der Regelfall, nicht die Ausnahme.

Punkt 3 ist der, der Abende kostet. Postgres springt nicht von 15 direkt auf 17, ohne dass man pg_upgrade oder einen Dump-und-Restore-Weg geht.

Automatische Updates, mit Augenmaß#

Werkzeuge wie Watchtower oder Diun können Updates ziehen bzw. melden. Die Frage ist nicht „automatisch oder nicht“, sondern welche Updates automatisch laufen dürfen.

ArtAutomatisch?Begründung
Patch-Updates (17.4.1 → 17.4.2)jaFehlerkorrekturen, kein Schemabruch
Nebenversion (17.4 → 17.5)mit Healthcheck und Benachrichtigungmeist harmlos, aber nicht immer
Hauptversion (17 → 18)nieMigrationen, geänderte Standards
Datenbankennie automatischsiehe oben

Wer benachrichtigt werden statt automatisch aktualisieren will, fährt mit einem reinen Melder besser:

services:
  diun:
    image: crazymax/diun:4.29
    restart: unless-stopped
    environment:
      DIUN_WATCH_SCHEDULE: "0 6 * * *"
      DIUN_PROVIDERS_DOCKER: "true"
      DIUN_NOTIF_TELEGRAM_TOKEN: ${TG_TOKEN}
      DIUN_NOTIF_TELEGRAM_CHATIDS: ${TG_CHAT}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - diun-daten:/data
Der Docker-Socket ist Root

Jeder Container mit Zugriff auf /var/run/docker.sock kann einen privilegierten Container starten und damit den Host übernehmen, :ro ändert daran nichts, weil der Socket eine API ist und keine Datei mit Rechten. Solche Container gehören auf ein Minimum begrenzt, und wenn, dann hinter einen Socket-Proxy, der nur Leseoperationen durchlässt.

Auf bekannte Lücken prüfen#

Ein Image mitzuführen heißt, dessen Betriebssystem mitzupflegen. Ein Scanner zeigt, wie alt der Unterbau tatsächlich ist:

# Trivy — prüft Paketstände im Image gegen bekannte Schwachstellen
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy:latest image --severity HIGH,CRITICAL postgres:17.4-alpine

Die Ausgabe ist beim ersten Mal ernüchternd, und das ist normal. Wichtig ist die Einordnung: Eine Lücke in einer Bibliothek, die der Dienst gar nicht aufruft, ist etwas anderes als eine im Webserver, der nach außen hört. Der Scanner liefert die Liste, die Bewertung bleibt Handarbeit.

Zwei Punkte, die den Aufwand dauerhaft senken:

  • Schlanke Basis-Images (-alpine, -slim, distroless) enthalten weniger Pakete und damit weniger Angriffsfläche.
  • Selbst gebaute Images regelmäßig neu bauen, auch ohne Codeänderung. Ein docker build --pull --no-cache zieht die aktuellen Systempakete nach.

Wenn ein Update wirklich schiefgeht#

# Was ist passiert? Logs seit dem Start des Containers
docker compose logs --since 15m app

# Läuft der Prozess, antwortet er nur nicht?
docker compose exec app sh -c 'ps aux; wget -qO- localhost:3000/healthz'

# Was hat sich am Image geändert?
docker image history app:1.9.0 --no-trunc | head -20

Und der wichtigste Reflex: nicht weiter nach vorne flüchten. Der Versuch, mit dem nächsthöheren Tag das Problem zu überspringen, führt fast immer weiter weg von einem funktionierenden Zustand. Zurück auf die alte Version, dann in Ruhe schauen.

Ein Rhythmus, der funktioniert#

WannWas
täglich, automatischMeldung über verfügbare Updates
wöchentlich, 15 MinutenPatch-Updates einspielen, Logs überfliegen
monatlich, 1 StundeNebenversionen, Scannerlauf, alte Images aufräumen
bei Bedarf, geplantHauptversionen, mit Zeitfenster und Dump

Der wöchentliche Termin ist der, der den Unterschied macht. Wer alle sechs Monate aktualisiert, hat kein Update mehr vor sich, sondern eine Migration.

MRMedia

Geschrieben aus dem laufenden Betrieb: MRMedia betreibt eigene Proxmox- Hosts, einen Backup-Server und Kundendienste in der EU. Fehler in einer Anleitung sind Fehler im eigenen Betrieb. Korrekturen bitte über Discord.