unattended-upgrades aktualisiert das Wirtssystem. Es sieht nicht in die Container hinein, dort steckt eine zweite, eigene Distribution mit eigenen Paketen, und die altert weiter, während apt auf dem Host beruhigende Meldungen schreibt.
Das ist eine Lücke mit Beruhigungswirkung: Die Update-Automatik läuft nachweislich, und trotzdem laufen ungepatchte Bibliotheken, nur eine Ebene tiefer. Bei Anwendungen, die direkt aus dem Internet erreichbar sind, ist genau diese Ebene die angegriffene.
Wie alt ist das, was hier läuft?#
# Erstellungsdatum je laufendem Image
for id in $(docker ps -q); do
img=$(docker inspect -f '{{.Config.Image}}' "$id")
created=$(docker image inspect -f '{{.Created}}' "$img" 2>/dev/null | cut -c1-10)
printf '%-45s %s\n' "$img" "${created:-unbekannt}"
done
# Welcher Digest läuft tatsächlich?
docker image inspect $(docker ps -q) --format '{{.RepoTags}} {{index .RepoDigests 0}}'
Alles, was älter als ein Vierteljahr ist, gehört auf die Liste. Nicht weil das Alter selbst ein Fehler ist, sondern weil in dieser Zeit sicher Sicherheitsupdates für die enthaltene Basis erschienen sind.
Tag oder Digest, beides, nicht eines#
Ein Tag wie 1.8 ist beweglich: Er kann auf ein neues Image zeigen, ohne dass sich etwas an deiner Konfiguration ändert. Das ist bequem und macht jeden Neustart unvorhersehbar; es ist außerdem der Weg, über den ein Lieferkettenangriff unbemerkt hereinkommt.
services:
app:
# Tag als Lesehilfe, Digest als Wahrheit
image: ghcr.io/beispiel/app:1.8.2@sha256:9c4f2b1a3d...
Damit ist der Stand festgelegt und die Aktualisierung wird zu einer bewussten Änderung, die man in der Versionsverwaltung sieht.
Aktualisieren als Ablauf#
# 1. Neuen Digest holen und notieren
docker pull ghcr.io/beispiel/app:1.8.3
docker image inspect ghcr.io/beispiel/app:1.8.3 --format '{{index .RepoDigests 0}}'
# 2. Alten Digest sichern — das ist der Rückweg
docker image inspect ghcr.io/beispiel/app:1.8.2 --format '{{index .RepoDigests 0}}' > /root/rueckweg-app.txt
# 3. Datenbank sichern (vor jedem Update, ohne Ausnahme)
docker compose exec -T db pg_dump -U app app | gzip > /var/backups/app-$(date +%F).sql.gz
# 4. Umstellen und beobachten
docker compose up -d app
docker compose logs -f --tail=100 app
Der Rückweg ist der Teil, der den Unterschied macht, dieselbe Logik wie in Wartungsfenster und der Weg zurück: Ein Update ohne Rückweg ist ein Versuch.
Werkzeuge, die laufende Container selbsttätig auf das neueste Image ziehen, klingen nach der Lösung und sind für Anwendungen mit Datenbankmigrationen riskant: Ein Hauptversionssprung um drei Uhr nachts, ohne Sicherung davor, ist kein Sicherheitsgewinn. Sinnvoller ist die Trennung, automatisch für Patchstände einer festgelegten Nebenversion, von Hand für alles darüber. Bei eigenen Images übernimmt ein Abhängigkeitsbot die Digest-Aktualisierung als Änderungsvorschlag, den man liest, bevor er läuft.
Eigene Images: regelmäßig neu bauen#
Ein Image, das man selbst baut, wird nicht durch das Aktualisieren des Basis-Images aktuell, es muss neu gebaut werden.
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
USER 10001:10001
# Wöchentlich, in der Bauumgebung
docker build --pull --no-cache -t registry.example.org/app:$(date +%Y%m%d) .
--pull holt das Basis-Image neu, --no-cache verhindert, dass die Paketinstallation aus dem Zwischenspeicher kommt. Ohne diese beiden Schalter baut man dasselbe alte Image ein weiteres Mal.
Was danach zu prüfen ist#
- Läuft der Dienst wirklich, nicht nur der Container? (
docker compose pssagt zu wenig.) - Wurde die Datenbank migriert, und ist der Dump von vorher noch vorhanden?
- Läuft der Container weiterhin ohne root (Container ohne root)?
- Steht der neue Digest in der Bestandsliste?
Die zwei Fragen vor jeder Image-Entscheidung#
Woher kommt das Image, und wer pflegt es? Ein offizielles Image eines Projekts wird bei Sicherheitsupdates der Basis in der Regel neu gebaut. Ein Image aus einem Sammelkonto, das jemand vor zwei Jahren hochgeladen hat, wird das nicht und genau solche Images findet man in Anleitungen, weil sie bequem waren. Ein Blick auf das Datum des letzten Neubaus beantwortet die Frage in Sekunden.
Wie groß ist die Basis? Jedes zusätzliche Paket im Image ist ein Paket, das in Meldungen auftauchen kann, auch wenn die Anwendung es nie ausführt. Schlanke Basen (-slim, alpine, distroless) verkleinern nicht nur den Download, sondern auch die Liste, die man nach einer Meldung durchsehen muss. Der Preis ist Fehlersuche ohne gewohnte Werkzeuge, ein Kompromiss, den man bewusst eingeht, statt ihn nachträglich zu bedauern.
Beide Antworten gehören neben den Digest in die Bestandsliste. Sie sind der Unterschied zwischen „wir setzen Version 1.8.2 ein“ und einer Aussage, die nach einer Meldung tatsächlich weiterhilft.
Geschrieben aus dem laufenden Betrieb: MRMedia betreibt eigene Hosts, Kundendienste und deren Härtung in der EU. Eine veraltete Anleitung ist hier ein Sicherheitsproblem, kein Schönheitsfehler. Korrekturen bitte über Discord.