Docker Engine 29: Was wirklich bricht
Die API-Untergrenze wurde zweimal verschoben, der Image Store wechselt nur bei Neuinstallationen, und das File-Descriptor-Limit jedes Containers fiel still von 1048576 auf 1024. Neun Monate Nachwirkungen, gegen Upstream geprüft.
- Docker
- containerd
- Linux
- DevOps
Docker Engine 29 erschien im November 2025 und legte eine bemerkenswerte Menge Software lahm, die jahrelang problemlos lief. Neun Monate später ist nicht der Bruch selbst das Interessante — sondern dass das meiste, was in den ersten zwei Wochen darüber geschrieben wurde, inzwischen falsch ist, weil die Änderung, die alle dokumentiert haben, drei Minor-Releases später teilweise zurückgenommen wurde. Wer diesen Monat einen Linux-Server aktualisiert, liest Ratschläge, die für eine Version von Docker 29 geschrieben wurden, die es nicht mehr gibt.

Das hier bricht tatsächlich, in der Reihenfolge, in der Sie es vermutlich antreffen, jeweils mit Lösung und Upstream-Quelle. Geschrieben für alle, die Docker Engine auf Linux-Servern betreiben — nicht für Docker Desktop, wo mehrere dieser Fragen anders zu beantworten sind. Die Diagnosebefehle lesen nur den Zustand; alles, was das System verändert, ist eine Konfigurationsänderung, eine Installation oder ein Neustart und als solches klar erkennbar. Wo Upstream kein Datum und keine Version veröffentlicht hat, steht das hier so, statt eines zu erfinden.
Beginnen Sie beim Symptom, nicht beim Changelog
Fast niemand landet über das Changelog auf dieser Seite. Man kommt über einen Fehlerstring, über einen Container, der nicht startet, oder über ein Dashboard, das die Umgebung als nicht erreichbar meldet. Deshalb steht diese Tabelle am Anfang: Suchen Sie Ihr Symptom und lesen Sie dann den Abschnitt, der es erklärt.[rel29]
| Was Sie sehen | Was es tatsächlich ist | Wo es behoben wird |
|---|---|---|
client version 1.24 is too old. Minimum supported API version is 1.44 | Ein Client, der älter ist als die API-Untergrenze des Daemons. Die Grenze stieg in 29.0.0 — und sank in 29.3.0 wieder. | Client aktualisieren. Das Override am Daemon ist eine Brücke, kein Fix. |
client version 1.52 is too new. Maximum supported API version is 1.44 | Das Spiegelbild: neuer Client gegen alten Daemon. Fast immer ein gepinnter Docker-in-Docker-Service in der CI. | DinD-Image auf dieselbe Major-Version wie den Daemon pinnen und beides gemeinsam bewegen. |
docker images ist nach der Installation von 29 leer | Der containerd image store ist bei Neuinstallationen der Default. Ihre overlay2-Inhalte sind verborgen, nicht gelöscht. | Backend zurückstellen oder bewusst exportieren und neu importieren. |
| Container stoßen an Verbindungsgrenzen, die es vorher nie gab | Das Standard-nofile-Limit in Containern fiel von 1048576 auf 1024. | --ulimit pro Container oder default-ulimits am Daemon. |
CI-Jobs scheitern an einem Service-Health-Check; No HOST or PORT found | Legacy-Link-Umgebungsvariablen werden nicht mehr injiziert, der Helfer, der auf den Service wartet, sieht ihn also nie. | Ein user-defined network und DNS. Die Escape-Hatch-Variable ist temporär. |
| Ein Container erreicht einen veröffentlichten Port, den er nicht erreichen können sollte | Die Chains DOCKER-ISOLATION-STAGE-1 und -STAGE-2 wurden in 29.0 entfernt. | Auf 127.0.0.1 statt auf die Wildcard-Adresse veröffentlichen. |
Ihre DOCKER-USER-Firewallregeln greifen nicht mehr | Passiert nur, wenn Sie das nftables-Backend aktiviert haben, das experimentell und opt-in ist. | Zurückwechseln oder eine eigene nftables-Table mit niedrigerer Priorität schreiben. |
Ein Go-Build scheitert an github.com/docker/docker | Das Modul ist nach github.com/moby/moby/client und .../api umgezogen. | Imports umschreiben; die Client-API hat gleichzeitig ihre Form geändert. |
# Three numbers decide everything that follows. Get them before you change
# anything, on the machine that is actually misbehaving.
# 1. Engine version, and the API floor this daemon enforces.
docker version
# Server: Docker Engine - Community
# Engine:
# Version: 29.3.0
# API version: 1.54 (minimum version 1.40)
# ^^^^
# Do NOT assume 1.44. Engine 29.0.0 raised the floor from 1.24 to 1.44, and
# 29.3.0 lowered it again to 1.40. Which one you get depends on where in the
# 29 series you landed, and almost everything written about this in late 2025
# predates the second change. Read the number, do not remember it.
# 2. Which image store this daemon is using.
docker info -f '{{ .DriverStatus }}'
# [[driver-type io.containerd.snapshotter.v1]] -> containerd store
# [[Backing Filesystem extfs] [Supports d_type true] ...] -> legacy overlay2
docker info 2>/dev/null | grep -i 'storage driver'
# Storage Driver: overlayfs -> containerd snapshotter
# Storage Driver: overlay2 -> graph driver
# 3. Which packet-filtering backend. nftables is opt-in and experimental in 29.x,
# so on an untouched host this should say iptables.
docker info 2>/dev/null | grep -i 'firewall'
# And the one that is not a Docker question, but constrains Docker anyway:
stat -fc %T /sys/fs/cgroup/
# cgroup2fs -> unified hierarchy, nothing to do
# tmpfs -> cgroup v1 or hybrid: deprecated in 29.0, supported until May 2029Drei dieser Zeilen sind dieselbe zugrunde liegende Änderung aus verschiedenen Blickwinkeln, und zwei davon sind an keiner Stelle dokumentiert, an der Sie nachsehen würden. Bevor Sie irgendetwas anfassen, klären Sie die drei Fakten, die bestimmen, welche davon auf Sie zutreffen.
Die API-Untergrenze wurde zweimal verschoben, und die Zahl, die Sie lesen, ist vermutlich falsch
Das ist die berühmte Änderung. Engine 29.0.0 hob die minimale Engine-API-Version, die der Daemon spricht, auf 1.44 an, was Docker 25.0 entspricht. Die vorherige Untergrenze war 1.24 — selbst erst in Docker 25.0 eingeführt, als Ablösung einer Untergrenze von 1.12, die seit 2016 gegolten hatte —, es war also die zweite Verschärfung in zwei Jahren und die erste, die überhaupt jemandem auffiel. Jeder Client, der eine ältere Version fest verdrahtet hatte oder nie gelernt hat zu verhandeln, hörte in dem Moment auf zu funktionieren, in dem der Daemon neu startete. Deshalb legte das Upgrade in derselben Woche Traefik, Portainer, Testcontainers, Watchtower, ein halbes Dutzend selbst gehosteter Dashboards und eine ganze Reihe von CI-Pipelines lahm.[pr51186][blog29]
Hier kommt der Teil, den fast kein veröffentlichter Artikel enthält. In 29.3.0, erschienen im März 2026, senkte Upstream die Untergrenze wieder — von 1.44 zurück auf 1.40, was Docker 19.03 entspricht. Der Default im aktuellen Quellcode ist 1.40, und 1.24 ist nur noch über ein explizites Override erreichbar. Ein Client mit API 1.41 oder 1.43, der im Dezember abgewiesen wurde, funktioniert heute also wieder, und eine Troubleshooting-Anleitung, die Ihnen 1.44 als Minimum nennt, beschreibt die ersten drei Minor-Linien einer Reihe, die längst darüber hinaus ist. Prüfen Sie Ihren eigenen Daemon statt der Erinnerung des Internets daran — die Zuordnung von Release zu API-Version ist veröffentlicht, und sie ist die einzige Fassung dieser Geschichte, die wahr bleibt.[pr52067][config][apimatrix]
| Daemon-Version | Minimale API-Version | Was das abweist |
|---|---|---|
| 25.0 – 28.x | 1.24 | Fast nichts. Ältere Engines hatten eine Untergrenze von 1.12, unverändert seit 2016. |
| 29.0.0 – 29.2.x | 1.44 | Jeden Client, der älter ist als Docker 25.0. Das ist die Bruchwelle, über die alle geschrieben haben. |
| 29.3.0 und neuer | 1.40 | Clients, die älter sind als Docker 19.03. Eine spürbare Lockerung, und fast nie erwähnt. |
| Beliebig, mit Override | herunter bis 1.24 | Kein Kommandozeilen-Flag: der Schlüssel in daemon.json oder DOCKER_MIN_API_VERSION. Upstream nennt es eine Option nur für Ausnahmefälle, ohne veröffentlichtes Entfernungsdatum. |
# The symptom, produced by the daemon, not the client:
#
# Error response from daemon: client version 1.24 is too old.
# Minimum supported API version is 1.44, please upgrade your client to a
# newer version
#
# And its mirror image, which appears in CI far more often than on servers -
# a NEW client talking to an OLD daemon, typically a pinned docker:dind service:
#
# Error response from daemon: client version 1.52 is too new.
# Maximum supported API version is 1.44
# The correct fix is always to update the client. Every tool listed later in
# this article shipped a build that negotiates the API version instead of
# hardcoding one.
# The escape hatch is for the case where the client is a vendor appliance you
# cannot update this week. It is set on the DAEMON, not on the client, and it
# is available in daemon.json only - there is no dockerd command-line flag.
cat /etc/docker/daemon.json 2>/dev/null # read it first, do not clobber other keys
# /etc/docker/daemon.json
{
"min-api-version": "1.24"
}
sudo systemctl restart docker
docker version | grep -i 'minimum version'
# The equivalent, if you would rather leave daemon.json alone:
sudo systemctl edit docker.service
# [Service]
# Environment="DOCKER_MIN_API_VERSION=1.24"
# Upstream is unambiguous about the status of both: API versions older than the
# default "are deprecated and to be removed in a future release", and the
# environment variable and configuration option "should only be used for
# exceptional cases". No removal date is published anywhere. Treat this as a
# bridge of unknown length, put a ticket on it, and do not let it become the
# permanent shape of your fleet.Eine Warnung zum Override, denn es ist die Art Einstellung, die den Vorfall überlebt, der sie hervorgebracht hat. Upstream bezeichnet ältere API-Versionen als deprecated und für die Entfernung in einem künftigen Release vorgesehen und beschreibt sowohl die Umgebungsvariable als auch den Konfigurationsschlüssel als ausschließlich für Ausnahmefälle gedacht. Ein Entfernungsdatum ist nicht veröffentlicht. Dockers eigene Policy sieht vor, ein als deprecated markiertes Feature mindestens ein stabiles Release lang zu behalten und bei Abkündigungen mehrere Monate Vorlauf anzustreben, das verschwindet also nicht über Nacht — aber "mindestens ein Release" ist eine Untergrenze und kein Plan. Nutzen Sie es, um eine Woche zu überstehen, nicht um eine Policy zu definieren.[config][lifecycle]
"Alle meine Images sind weg" ist eine Neuinstallation, kein Upgrade
Das zweithäufigste gemeldete Problem ist das alarmierendste und das ungefährlichste: Sie installieren Docker 29, führen docker images aus, und die Liste ist leer. Es wurde nichts gelöscht. Engine 29 macht den containerd image store zum Standard-Backend, aber — und dieser Satz löst nahezu jede Meldung auf — nur bei Neuinstallationen. Ein In-Place-Upgrade der Pakete behält den overlay2-Graph-Driver und zeigt Ihnen Ihre Images weiterhin an. Die Pakete zu purgen und neu zu installieren zählt als frisch, ebenso das Neuaufsetzen des Hosts aus dem Configuration Management, und genau so begegnen die meisten dieser Sache, ohne den Eindruck zu haben, etwas Ungewöhnliches getan zu haben.[cstore]
| Frage | overlay2 (Graph Driver) | containerd Snapshotter |
|---|---|---|
| Wann Sie es bekommen | Jedes Upgrade von 28 oder älter behält es | Neuinstallationen ab 29.0, außer mit userns-remap |
docker info meldet | Storage Driver: overlay2 | Storage Driver: overlayfs und einen driver-type von io.containerd.snapshotter.v1 |
| Wo die Inhalte liegen | Unter data-root, normalerweise /var/lib/docker | In containerds eigenem Storage, den data-root nicht verschiebt |
| Plattenbedarf | Nur unkomprimierte Layer | Komprimiert und unkomprimiert, also merklich mehr für dieselben Images |
| Multi-Platform-Images und Attestations | Nicht unterstützt | Unterstützt — der eigentliche Grund für den Wechsel des Defaults |
| Wechsel zwischen beiden | Verbirgt Images und Container des anderen Backends | Umgekehrt genauso. Nichts wird gelöscht; es gibt keine In-Place-Konvertierung |
# "I upgraded and all my images are gone."
#
# Almost every report of this turns out to be a REINSTALL, not an upgrade.
# Docker 29 makes the containerd image store the default on fresh installations
# only; an in-place package upgrade keeps the overlay2 graph driver. Purging the
# packages and installing them again counts as fresh, and so does rebuilding the
# host from your configuration management.
#
# Nothing has been deleted. The two backends cannot see each other's content:
# switching "temporarily hides images and containers created with the other
# backend. Your data remains on disk."
docker info -f '{{ .DriverStatus }}'
docker system df
# To see the old content again, point the daemon back at where it lives:
# /etc/docker/daemon.json
{
"features": { "containerd-snapshotter": false },
"storage-driver": "overlay2"
}
sudo systemctl restart docker
# Understand what this buys you: the legacy graph drivers are themselves now
# deprecated, and Docker states the graph driver backend will be removed in a
# future release. Switching back is a stay of execution, not a destination.
# Moving forward deliberately instead. There is no supported in-place
# conversion: the documented paths are a registry round trip, or save/load.
docker save -o /var/tmp/keep.tar app:1.4 app:1.5
# ... switch the daemon to the containerd store, restart, then:
docker load -i /var/tmp/keep.tar
# An experimental automatic switch exists. Read what it actually does before
# using it: it only fires when there are NO containers at all and the image
# count is at or below the threshold you set.
# /etc/docker/daemon.json
{
"features": { "containerd-migration": true }
}
# and, for the threshold, a systemd drop-in:
# Environment="DOCKER_MIGRATE_SNAPSHOTTER_THRESHOLD=5"
# The documentation labels this experimental and tells you to take backups
# first. On a server, exporting the handful of images you actually care about
# is less work than recovering from a migration that half-happened.
# Two operational consequences that surface weeks later, not on day one:
#
# - The containerd store keeps layers both compressed and uncompressed, so the
# same images occupy noticeably more disk than they did under overlay2.
#
# - "data-root" in daemon.json does NOT move containerd's content. If you put
# /var/lib/docker on its own partition, containerd's storage is configured
# separately - otherwise it quietly fills the root filesystem instead, which
# is a page you will read at 03:00 rather than now.
#
# - The containerd store is unavailable when user-namespace remapping is
# enabled. That is a known bug, not a policy, and userns-remap hosts are
# excluded from the fresh-install default for exactly that reason.Die beiden Backends sehen die Inhalte des jeweils anderen nicht. Upstream sagt ausdrücklich, dass ein Wechsel "temporarily hides images and containers created with the other backend" und dass "your data remains on disk". Wenn Sie gerade gewechselt haben und die Liste leer aussieht: Fangen Sie nicht an zu prunen, um Platz freizugeben — Sie würden die Hälfte löschen, die Sie sehen, und weiter für die Hälfte bezahlen, die Sie nicht sehen.
Zwei Folgen zeigen sich eher nach Wochen als am selben Tag. Der containerd store hält Layer sowohl komprimiert als auch unkomprimiert vor, dieselben Images belegen also spürbar mehr Platte als unter overlay2. Und data-root in daemon.json verschiebt containerds Inhalte nicht: Wenn Sie /var/lib/docker sorgfältig auf ein eigenes Volume gelegt haben, wird containerds Storage separat konfiguriert und läuft andernfalls stattdessen das Root-Dateisystem voll. Es gibt außerdem einen Fall, in dem der neue Default schlicht nicht greift — Hosts mit User-Namespace-Remapping sind ausgenommen, wegen eines offenen Bugs und nicht aufgrund einer Policy-Entscheidung. Und seien Sie sich im Klaren darüber, was ein Zurückwechseln Ihnen einbringt: Die alten Graph Driver sind selbst deprecated, und Docker hat angekündigt, das Backend in einem künftigen Release zu entfernen. Zurück auf overlay2 zu gehen ist ein Aufschub, keine Lösung.[daemon][iss47377]
Das File-Descriptor-Limit jedes Containers fiel auf 1024
Das ist die Änderung, bei der ich darauf wetten würde, dass sie am häufigsten falsch diagnostiziert wird, denn sie steht überhaupt nicht im Abschnitt zu den Breaking Changes. Sie ist ein Unterpunkt unter einem containerd-Versionssprung. Docker Engine 29.0.0 lieferte containerd 2.1.5 mit, und das setzt LimitNOFILE=infinity in seiner systemd-Unit nicht mehr — das Standard-Soft-Limit für offene Dateien in jedem Container fällt damit von 1048576 auf 1024. Spätere 29.x-Releases bringen neuere containerd-Versionen mit, und alle behalten das neue Verhalten bei.[iss51485][ctd215]
# The 29.0 change with the widest blast radius is not under a "breaking
# changes" heading at all. It is a sub-bullet under a containerd version bump.
docker run --rm ubuntu:24.04 bash -c 'ulimit -n; ulimit -Hn'
# soft 1048576 with containerd.io 1.7.x (Docker 28 and earlier)
# soft 1024 with containerd.io 2.1.5, shipped with Docker Engine 29.0.0
#
# Only the SOFT limit is documented as changing. The hard limit becomes
# whatever systemd's DefaultLimitNOFILE gives you on that host, so read both
# numbers on your own machine rather than trusting a value from an article.
# What happened: containerd 2.1.5 stopped setting LimitNOFILE=infinity in its
# systemd unit, so containers now inherit systemd's ordinary default. Docker
# Engine made the same change for BUILD containers back in v25.0; version 29
# extends it to every container. The reasoning is sound - with an effectively
# unlimited value, software that sizes its own buffers from `ulimit -n`
# (MySQL is the classic case) could consume the machine - and it is documented
# in the release notes. It is simply not where anyone looks.
# 1024 is a real ceiling, and the failures it produces rarely mention file
# descriptors: an Nginx worker refusing connections at a number that looks
# arbitrary, a JVM selector loop throwing at load, a connection pool that
# stalls under exactly the traffic it handled last week.
# Per container, which is the right place if only one workload needs it:
docker run --ulimit nofile=65535:65535 ...
# Or restore a default for every container on the host:
# /etc/docker/daemon.json
{
"default-ulimits": {
"nofile": { "Name": "nofile", "Soft": 65535, "Hard": 65535 }
}
}
# The release notes show 1048576 in this example, which restores the exact old
# behaviour. Prefer a considered number. Copying 1048576 back wholesale also
# copies back the problem the change was made to solve, and you will not be the
# one who remembers that in two years.Die Begründung dahinter ist gut, und die Maintainer erklären sie klar: Ein faktisch unbegrenzter Wert konnte in Kombination mit einer Eigenart der Go-Runtime das Soft Limit auf das Hard Limit anheben, und Software, die ihre eigenen Puffer aus ulimit -n dimensioniert — MySQL ist das stehende Beispiel —, frisst dann die Maschine auf. Für Build-Container hat Docker dieselbe Änderung schon in v25.0 vorgenommen; Version 29 dehnt sie einfach auf alle aus. Das Problem ist nicht die Änderung, sondern dass 1024 eine echte Obergrenze ist und die Fehler, die sie erzeugt, fast nie von File Descriptors sprechen. Sie bekommen einen Nginx-Worker, der Verbindungen bei einer willkürlich wirkenden Zahl ablehnt, einen JVM-Selector, der unter Last fliegt, einen Pool, der genau bei dem Traffic stehen bleibt, den er letzte Woche noch bewältigt hat. Wenn nach dem Upgrade etwas unter Last langsamer oder instabiler wurde, ohne dass sich Code geändert hat, prüfen Sie das zuerst.[rel29]
Legacy-Link-Variablen sind weg, und die CI hat es zuerst gemerkt
Docker 29 injiziert die Legacy-Link-Umgebungsvariablen nicht mehr in Container — DB_PORT_5432_TCP_ADDR und ihre ganze Verwandtschaft. Sie sind seit Jahren deprecated, und der Ersatz, DNS in einem user-defined network, funktioniert seit 2016. Das Problem ist, dass zahlreiche Container-Entrypoint-Skripte und CI-Helfer sie weiterhin lesen und der resultierende Fehler überhaupt nichts über Docker sagt. Der klassische Fall ist GitLab Runner: Der Helper-Container, der darauf wartet, dass ein Eintrag unter services: hochkommt, bricht mit FATAL: No HOST or PORT found ab, der Job scheitert also an einem Health Check, während der Service-Container selbst völlig einwandfrei läuft.[pr50719][gllinks]
# Docker 29 stopped injecting the legacy link environment variables:
# DB_PORT_5432_TCP_ADDR, DB_PORT_5432_TCP_PORT, DB_NAME, DB_ENV_*
# They were deprecated years ago. A surprising number of CI images and entry
# point scripts still read them, and the failure does not mention Docker.
# The symptom in a GitLab CI job with a services: block is a health check that
# never passes: the runner's wait-for-service helper exits 1 with
# FATAL: No HOST or PORT found
# and the job fails before your script runs. The service container itself
# started perfectly well - which is why this looks like an infrastructure
# outage rather than a Docker change.
# Confirm it in two lines. The db container must be on the DEFAULT BRIDGE:
# legacy links do not work on user-defined networks at all.
docker run -d --name db postgres:18
docker run --rm --link db:db alpine env | grep -c '^DB_PORT_'
# 0 on Engine 29, non-zero before it
# The fix is to stop parsing those variables. DNS on a user-defined network has
# worked since 2016, does not need --link at all, and survives restarts:
docker rm -f db
docker network create appnet
docker run -d --name db --network appnet postgres:18
docker run --rm --network appnet postgres:18 pg_isready -h db
# In Compose this is already the default: services on the same project network
# resolve each other by service name.
# The escape hatch, when the image is not yours to change and the release is
# next week. Daemon-side, and explicitly temporary:
sudo systemctl edit docker.service
# [Service]
# Environment="DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS=1"
sudo systemctl restart docker
# Upstream wording: "the escape hatch will be removed in a later version".Diese Sache hat eine echte Schärfe: Zum Zeitpunkt des Schreibens ist das entsprechende GitLab-Runner-Issue noch offen, und der dokumentierte Workaround besteht darin, entweder die Escape-Hatch-Umgebungsvariable am Daemon zu setzen oder die Docker-Version des Runners zu pinnen. Wenn Ihre CI Service-Container nutzt, testen Sie das auf einem Runner, bevor Sie das Upgrade über die ganze Flotte ausrollen — nicht weil es schwer zu beheben wäre, sondern weil der Fehler in allen Jobs gleichzeitig auftaucht und wie ein Infrastrukturausfall aussieht statt wie eine Konfigurationsänderung.
Die Isolation-Chains wurden entfernt, und das ändert die Erreichbarkeit
Das ist die Änderung mit dem wenigsten Lärm und den größten Folgen. Engine 29 hat die iptables-Regeln für Bridge-Netzwerke überarbeitet und die Chains DOCKER-ISOLATION-STAGE-1 und DOCKER-ISOLATION-STAGE-2 vollständig entfernt. Die Release Notes benennen die Auswirkungen unmissverständlich, und man sollte sie zweimal lesen, wenn man getrennte Bridge-Netzwerke je als Sicherheitsgrenze betrachtet hat: Container können jetzt Ports erreichen, die Container aus anderen Netzwerken auf Host-Adressen veröffentlicht haben, sofern der Userland-Proxy nicht läuft, und ebenso Ports auf Container-Adressen in anderen Netzwerken, die den Gateway-Modus nat-unprotected verwenden.[pr49981][packet]
# 29.0 reworked the iptables rules for bridge networks and removed the
# DOCKER-ISOLATION-STAGE-1 and DOCKER-ISOLATION-STAGE-2 chains. The release
# notes state the two consequences plainly:
#
# - containers can now access ports published to host addresses by containers
# in other networks when the userland proxy is not running
# - containers can now access ports on container addresses in other networks
# that have gateway mode "nat-unprotected"
#
# If you were using separate bridge networks as a security boundary, re-read
# that. It is a reachability change, and nothing in the upgrade tells you.
# Find every port you publish to a wildcard address. Each one is a port that
# containers in other networks may now reach through the host.
docker ps --format '{{.Names}} {{.Ports}}' | grep '0.0.0.0'
# The durable fix is to stop publishing to the wildcard when you only meant
# localhost. This has always been the correct form and does not depend on any
# chain existing:
docker run -p 127.0.0.1:5432:5432 postgres:18
# In Compose:
# ports:
# - "127.0.0.1:5432:5432"
# Check the current shape of the rules rather than the shape you remember:
sudo iptables -S | grep -c 'DOCKER-ISOLATION' # 0 on Engine 29
sudo iptables -S DOCKER-USER
# DOCKER-USER still exists and still works under the default iptables backend.
# It is only absent if you deliberately switch the daemon to nftables - which
# is the next section, and the answer there is "not on a production host yet".| Verhalten | Bis 28.x | Ab 29.0 | Was zu tun ist |
|---|---|---|---|
| Isolation-Chains für Bridge-Netzwerke | DOCKER-ISOLATION-STAGE-1 und -STAGE-2 | Entfernt | Alles erneut prüfen, was auf eine Wildcard-Adresse veröffentlicht wird. |
| Legacy-Link-Umgebungsvariablen | Automatisch injiziert | Nicht injiziert | DNS in einem user-defined network nutzen. |
DOCKER-USER-Chain | Vorhanden | Unter iptables vorhanden, unter nftables nicht | Das nftables-Backend nicht aktivieren, wenn Sie darauf angewiesen sind. |
| SCTP-Checksum-Mangle-Regel | Nur mit DOCKER_IPTABLES_SCTP_CHECKSUM=1 | Entfernt | Die Variable hat jetzt überhaupt keine Wirkung mehr. |
| Default-Gateway bei macvlan und ipvlan-l2 | Abgeleitet | Nur wenn --gateway in der IPAM-Konfiguration steht | Das Gateway in der Netzwerkdefinition explizit setzen. |
| Verschlüsselte Overlay-Netzwerke | Kaputt in 28.2.2 und 25.0.13–14 | Kaputt in 29.0.0 bis 29.2.0 | 29.2.1 behebt es, aber ein gepatchter Node kann keinen Verkehr zu einem ungepatchten tragen. Bewegen Sie den gesamten Swarm gemeinsam von den betroffenen Builds weg. |
Nichts im Upgrade sagt Ihnen, dass das passiert ist, und Ihr Monitoring auch nicht — es ist eine Ausweitung der Erreichbarkeit, kein Fehler. Die haltbare Antwort ist nicht, die Chains wiederherzustellen, sondern aufzuhören, auf die Wildcard-Adresse zu veröffentlichen, wenn localhost gemeint war; das war immer die korrekte Form und hängt von keiner existierenden Chain ab. Wenn Sie ohnehin dabei sind, ist eine Downgrade-Falle notierenswert: Ein Netzwerk, das auf 29 durch Anforderung einer Präfixlänge aus den Default Address Pools angelegt wurde, ist auf einem älteren Daemon unbrauchbar und muss gelöscht und neu erstellt werden — halten Sie Ihre Netzwerkdefinitionen also vorher fest, nicht hinterher.[pr50114]
nftables ist real, experimentell und hat keine DOCKER-USER-Chain
Engine 29 hat außerdem ein nftables-Backend eingeführt, und man sollte bei dessen Status präzise sein, weil die Berichterstattung es nicht war: Es ist experimentell und opt-in, der Default bleibt iptables, und es lässt sich überhaupt nicht aktivieren, solange der Daemon im Swarm-Modus läuft. Upstream sagt unverblümt, dass Konfigurationsoptionen, Verhalten und Implementierung sich allesamt ändern können. Auf einer aktuellen Distribution werden Ihre iptables-Regeln ohnehin meist von der nftables-Kernel-Maschinerie ausgeführt, ein Wechsel bringt Ihnen heute also sehr wenig. Der Grund, jetzt davon zu wissen, ist die Wirkung auf Ihre Firewall: In Dockers nftables-Implementierung gibt es keine DOCKER-USER-Chain, und Ihre Regeln werden nicht in die neuen Tables migriert. Ob sie überhaupt noch greifen, hängt von der Vorgeschichte ab: Beim Umschalten eines bestehenden Hosts bleibt der alte Sprung aus FORWARD bestehen, die Regeln feuern also weiter, bis dieser Sprung verschwindet oder der Host neu startet, während ein Host, der von Anfang an auf nftables lief, den Sprung nie hatte und sie stillschweigend ignoriert. Zwei Maschinen mit identischer Konfiguration, zwei verschiedene Firewalls.[nft][pr50476]
# The nftables backend in 29.x is EXPERIMENTAL and opt-in. The default is still
# iptables, which on a current distribution is usually iptables-nft underneath
# anyway - so you are already using the nftables kernel machinery either way.
# Upstream: "configuration options, behavior and implementation may all change
# in future releases", and it "cannot be enabled when the Docker daemon is
# running in Swarm mode".
docker info 2>/dev/null | grep -i 'firewall'
# Opting in, if you are testing it somewhere that is not production:
# /etc/docker/daemon.json
{
"firewall-backend": "nftables"
}
sudo systemctl restart docker
sudo nft list tables
# table ip docker-bridges
# table ip6 docker-bridges
# The part that quietly changes your security posture:
# "In Docker's nftables implementation, there is no DOCKER-USER chain."
# Your rules are not migrated. Whether they still run depends on history:
# switching an existing host to nftables leaves the old FORWARD jump to
# DOCKER-USER in place, so those rules keep firing until the jump is removed
# or the host reboots. A host that started on nftables never had the jump, so
# the same rules do nothing at all. Both states look identical in your
# configuration management, which is the dangerous part.
sudo iptables -S FORWARD | grep DOCKER-USER # if this prints, you have both worlds
# The replacement is your own table, with base chains of the same type and hook
# as Docker's and a LOWER priority number, so yours run first. "filter" is the
# same priority Docker uses, so subtract from it:
#
# table ip my-filter {
# chain my-forward {
# type filter hook forward priority filter - 1; policy accept;
# iifname "eth0" ip saddr != 192.0.2.2 counter drop
# }
# }
# One more difference that catches everybody: in nftables an accept is not
# final, so you cannot permit something Docker drops simply by accepting it
# earlier. Use a firewall mark and tell the daemon to honour it:
# dockerd --bridge-accept-fwmark=1
# dockerd --bridge-accept-fwmark=0x1/0x3 # with a maskgithub.com/docker/docker ist nicht mehr Ihr Import-Pfad
Wenn Sie die Docker-API aus Go heraus nutzen, ist Version 29 eher ein Rewrite als ein Versionssprung, und es ist die einzige Änderung auf dieser Seite, die sich nicht mit einem Konfigurationsschlüssel umgehen lässt. Das Modul github.com/docker/docker ist zugunsten von github.com/moby/moby/client und github.com/moby/moby/api deprecated; das Parent-Modul ist jetzt ausdrücklich ein internes Implementierungsdetail, und Releases werden mit dem Präfix docker- getaggt. Zusätzlich zum Umzug hat die Client-API selbst ihre Form geändert:[rel29]
- Options-Structs ersetzen Positionsargumente bei Image-, Config- und Prune-Operationen. Das ist eine mechanische, aber breite Änderung — sie berührt in einer typischen Integration nahezu jede Aufrufstelle.
- Rückgabewerte sind in Structs gekapselt.
ImageInspect,ImageHistory,ImageLoadundImageSaveliefern nicht mehr das, was sie früher lieferten, undImagePullundImagePushgeben Objekte zurück, die Message-Iteratoren bereitstellen. - Filter sind umgezogen. Der Client hat einen eigenen Filter-Typ, alles, was das alte filters-Paket importiert, muss also refaktoriert und nicht bloß umgebogen werden.
- Adressen sind typisiert. IP-Adressen und Subnetze sind jetzt
netip.Addrundnetip.Prefixstatt Strings undnet.IPNet, was eine echte Verbesserung und ein echter Vormittag Arbeit ist. client.ImageCreateist weg und wurde durchImagePulloderImageImportersetzt, je nachdem, wofür Sie es tatsächlich verwendet haben.
cgroup v1 ist deprecated, mit einem echten Datum
Docker 29 erklärt cgroup v1 für deprecated — und ungewöhnlich für eine Deprecation kommt sie mit einem Datum. Upstream gibt an, dass der Support bis Mai 2029 weiterläuft und dass das letzte Release im Mai 2029 cgroup v1 selbst möglicherweise nicht mehr unterstützt, mindestens ein gepflegter Branch aber schon. Das ist eine wirklich großzügige Vorlaufzeit, und es bedeutet, dass deswegen dieses Jahr nichts auf Ihren Servern aufhört zu funktionieren.[deprecated][iss51111]
Trotzdem lohnt es sich, früh zu handeln, aus einem Grund, der mit Dockers Zeitplan nichts zu tun hat: Ihre Distribution ist zuerst dort. systemd hat die Legacy- und Hybrid-Hierarchien in v258 entfernt; ein Host, der noch mit systemd.unified_cgroup_hierarchy=0 bootet — meist ein Parameter, den vor Jahren jemand hinzugefügt hat, um eine alte Runtime zufriedenzustellen, und dann nie wieder entfernte —, läuft lange vor 2029 auf Betriebssystemebene gegen diese Wand. Der Parameter ist trivial zu finden und meist trivial zu entfernen; unangenehm ist nur, ihn während eines Wartungsfensters zu entdecken statt an einem gewöhnlichen Dienstag.[systemd258][cgroupv2]
Das Ökosystem: was brach und in welcher Version es behoben wurde
Die Kompatibilitätstabelle ist der Teil dieser Geschichte, der wirklich gut dokumentiert ist, größtenteils weil Portainer die Folgen im Detail aufgeschrieben hat, während sie passierten. Das meiste davon war innerhalb von Wochen behoben. Der Wert dieser Liste liegt heute nicht in den Fixes, sondern darin zu bemerken, welche dieser Dinge Sie betreiben.[portblog]
| Werkzeug | Was brach | Behoben in |
|---|---|---|
| Traefik | Der Docker-Provider hatte API-Version 1.24 fest verdrahtet, es wurden also überhaupt keine Routen konfiguriert. | 3.6.1 — die feste Version wurde durch Verhandlung ersetzt. |
| Portainer | Der mitgelieferte Client war ohne Aushandlung auf API 1.41 gedeckelt, und eine strikte Mindestversionsprüfung verweigerte die Verbindung; Umgebungen erschienen als nicht erreichbar. | 2.33.5 LTS / 2.36.0 STS. |
| Testcontainers für Java | docker-java nutzte standardmäßig API 1.32; Tests fanden keine gültige Docker-Umgebung. | 2.0.2, das ein verhandelndes docker-java mitbringt. |
| Docker SDK für Python | DEFAULT_DOCKER_API_VERSION stand in 6.1.3 auf 1.41. | 7.1.0 (1.44). Besser noch: verhandeln statt pinnen. |
| GitLab Runner | Versionskonflikte zwischen Runner, Job-Image und DinD-Service; davon getrennt scheitern Service-Health-Checks mit No HOST or PORT found. | DinD-Image pinnen oder die API-Version setzen. Das Health-Check-Issue war zum Zeitpunkt des Schreibens noch offen. |
| JetBrains-IDEs | Das Docker-Plugin wurde von einem v29-Daemon abgewiesen. | Plugin-Build 253.28294.x, ausgeliefert in 2025.2.5 und in den 2025.3-Releases. |
| Watchtower (containrrr) | Auf API 1.25 gepinnt, in einer Restart-Schleife gegen einen v29-Daemon. | Kein Upstream-Fix. Das Repository wurde im Dezember 2025 schreibgeschützt archiviert. |
| CapRover | docker-modem auf API 1.43 gepinnt — eine Version unter der Untergrenze von 29.0. | 1.14.1. |
Ansible community.docker | KeyError: 'ApiVersion' durch die geänderte JSON-Schreibweise in docker version. | Engine 29.0.1 stellte die früheren Feldnamen wieder her. |
| Docker Compose | Sehr alte Builds liegen unter der API-Untergrenze; eine Kompatibilitätsmatrix ist nicht veröffentlicht. | Das aktuelle docker-compose-plugin aus demselben Repository wie die Engine installieren. |
Das Muster ist durchgehend dasselbe: Alles, was kaputtging, hatte irgendwo eine fest verdrahtete API-Version, statt eine auszuhandeln, und alles, was repariert wurde, wurde durch nachgerüstete Aushandlung repariert. Portainer ist die deutlichste Illustration: Der mitgelieferte Client war auf API 1.41 gedeckelt und handelte gar nicht erst aus, und eine strikte Prüfung gegen das vom Daemon gemeldete Minimum verweigerte die Verbindung rundheraus — auf einem Daemon, der durchaus bereit gewesen wäre, mit einem Client zu reden, der ordentlich fragt. GitLab Runner ist aus einem anderen Grund unangenehm, weil Runner, Job-Image und der Docker-in-Docker-Service jeweils einen eigenen Client mitbringen und je zwei davon auf verschiedenen Seiten der Untergrenze landen können. Die praktische Regel dort lautet, das DinD-Service-Image auf dieselbe Major-Version zu pinnen wie den Daemon, der es ausführt, und beides gemeinsam zu ändern.[traefik][testcontainers][glrunner]
Der Rest der Liste ist ein brauchbares Audit Ihrer eigenen Lieferkette. Watchtower ist der lehrreiche Fall: auf API 1.25 gepinnt, kein offizieller Fix, und das Repository wurde im Dezember 2025 schreibgeschützt archiviert — es war also ein unbeaufsichtigter Prozess mit dauerhaftem Zugriff auf Ihren Docker-Socket, der aufhörte zu funktionieren, und es gibt niemanden mehr, der das beheben könnte. Darüber lohnt es sich unabhängig von Docker 29 einen Moment nachzudenken. Wenn ein Werkzeug Ihren Socket hat, ist sein Wartungszustand Teil Ihrer Sicherheitslage, und dieses Upgrade war ein ungewöhnlich klares Audit darüber, welche Ihrer Werkzeuge noch Maintainer haben.[watchtower][portainer][jetbrains]
Upgrade, Pinning und der Weg zurück
Nichts an der Mechanik hier ist raffiniert. Das Upgrade ist eine gewöhnliche apt- oder dnf-Transaktion mit den üblichen fünf Paketen, und der dokumentierte Weg, eine bestimmte Version zu installieren, ist der dokumentierte Weg zurück zu einer. Worauf es ankommt, ist die Reihenfolge, und vorher zu wissen, was ein Rollback nicht rückgängig macht.[installubuntu][installrhel]
# Upgrading is an ordinary package transaction. The care is in the order, and
# in knowing in advance what a rollback will and will not undo.
# --- Debian / Ubuntu ---------------------------------------------------------
apt list --all-versions docker-ce | head
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Staying on 28 deliberately - read the last section before choosing this:
sudo apt-mark hold docker-ce docker-ce-cli containerd.io
apt-mark showhold
# Going back. Take the exact version strings from the lists above. Pin
# containerd.io too: leaving it unpinned keeps containerd 2.x and its systemd
# unit, so the file-descriptor change described earlier survives the rollback.
apt list --all-versions containerd.io | head
VERSION_STRING=5:28.5.2-1~ubuntu.24.04~noble
CONTAINERD_STRING=1.7.29-1~ubuntu.24.04~noble
sudo apt install docker-ce="$VERSION_STRING" docker-ce-cli="$VERSION_STRING" \
containerd.io="$CONTAINERD_STRING" \
docker-buildx-plugin docker-compose-plugin
# --- RHEL / Rocky / AlmaLinux / Fedora ---------------------------------------
dnf list docker-ce --showduplicates | sort -r | head
sudo dnf install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
# A specific version, in the same shape as the apt form:
sudo dnf install docker-ce-3:28.5.2-1.el9 docker-ce-cli-3:28.5.2-1.el9 \
containerd.io docker-buildx-plugin docker-compose-plugin
# Pinning is a dnf feature, not a Docker one, and the plugin package name
# depends on which dnf you have:
# RHEL / Rocky / Alma (dnf4): sudo dnf install python3-dnf-plugin-versionlock
# Fedora (dnf5): sudo dnf install dnf5-plugin-versionlock
# then: sudo dnf versionlock add docker-ce docker-ce-cli containerd.io
# Four things a downgrade does not undo:
#
# 1. If the daemon was switched to the containerd image store, a 28.x daemon
# cannot see those images. They are still on disk; they are not in the
# graph driver, and there is no conversion.
# 2. A network created on 29 by asking the default pool for a prefix size
# (--subnet 0.0.0.0/24) is unusable on an older daemon. It has to be
# deleted and recreated - so record your network definitions before you
# start, not after.
# 3. Rootless installations from 29.5 onwards no longer receive slirp4netns
# through Docker packaging. Reinstall it from your distribution if you go
# back to a build that expects it.
# 4. The container file-descriptor limit, unless you pinned containerd.io as
# well. That change lives in containerd's systemd unit, not in dockerd.Noch etwas, falls Ihre Automatisierung Dockers Ausgabe parst, statt die API aufzurufen: 29.0.0 änderte die Schreibweise der Felder in docker version --format=json, was Ansibles Docker-Collection mit einem nackten KeyError zerlegte. Das wurde in 29.0.1 korrigiert, betrifft also nur, wer noch auf dem allerersten Release sitzt — aber es ist eine gute Erinnerung daran, dass --format json eine Schnittstelle mit einer Version ist und dass das Pinnen auf ein Patch-Release eine billige Versicherung für alles ist, was sie ausliest.[ansible]
Richtig verifizieren
"Der Daemon ist gestartet" ist keine Verifikation. Führen Sie das hier vor dem Upgrade aus, heben Sie die Ausgabe auf, führen Sie es danach erneut aus und vergleichen Sie beides — der Punkt ist, dass das meiste, was sich in Version 29 geändert hat, ein Default ist, und ein geänderter Default erzeugt eine andere Ausgabe statt eines Fehlers.
#!/usr/bin/env bash
# docker29-check.sh - Run it BEFORE the upgrade, keep the output, run it again
# afterwards and diff the two.
#
# Everything here only reads state, with one exception: the ulimit probe starts
# a throwaway container and will PULL ubuntu:24.04 if the host does not already
# have it. On a metered link, or on a host where you are watching disk, pull it
# in advance or swap in an image you already have.
set -uo pipefail
echo "=== engine, api floor, client ==="
docker version
echo "=== image store, storage driver, cgroup driver, firewall backend ==="
docker info 2>/dev/null | grep -Ei 'storage driver|driver-type|cgroup|firewall|logging driver|userns'
echo "=== the ulimit that changed underneath you ==="
docker run --rm ubuntu:24.04 bash -c 'ulimit -n; ulimit -Hn'
echo "=== everything that should be running, is ==="
docker ps --format '{{.Names}} {{.Status}} {{.Image}}' | sort
echo "=== images the daemon can actually see, and how much space they take ==="
docker images --format '{{.Repository}}:{{.Tag}}' | sort | head -50
docker system df
echo "=== published ports, from the host's point of view ==="
# -p needs root; without it you get the ports but not the owning process.
sudo ss -tlnp 2>/dev/null
echo "=== packet filtering: which world are we in ==="
sudo iptables -S DOCKER-USER 2>/dev/null || echo "no DOCKER-USER chain"
sudo iptables -S | grep -c DOCKER-ISOLATION
sudo nft list tables 2>/dev/null || echo "nft binary not present"
echo "=== every client that talks to this daemon ==="
# Anything listed here is a candidate for the API floor problem: an agent, a
# management UI, a CI runner, a monitoring exporter.
sudo ss -xp 2>/dev/null | grep docker.sock | sort -uZwei Dinge kann das Skript nicht für Sie prüfen. Erstens Compose: Es gibt keine veröffentlichte Kompatibilitätsmatrix zwischen Compose-Versionen und Engine-API-Versionen, und das Upstream-Issue, das eine forderte, wurde als Frage geschlossen; der einzig verlässliche Weg ist daher, das aktuelle docker-compose-plugin aus demselben Repository wie die Engine zu installieren, statt darüber zu spekulieren, welcher alte Build vielleicht noch funktioniert. Zweitens jeder Prozess, der Ihren Docker-Socket hält: ein Agent, ein Exporter, eine Management-UI, ein CI-Runner. Jeder davon bringt seinen eigenen API-Client mit, und jeder ist ein Kandidat für den ersten Abschnitt dieses Artikels.[compose]
Sollten Sie also upgraden?
Die Antwort ist ja, und der Grund ist nicht die Feature-Liste. Upstream markiert docker-28.x als unmaintained und definiert das als nicht mehr aktiv entwickelt, keine Beiträge annehmend und außerhalb des Geltungsbereichs von Security Advisories. Der einzige weitere gepflegte Branch ist 25.0, der für zwei bestimmte Downstream-Distributionen am Leben gehalten wird und dessen Wartung voraussichtlich im Dezember 2026 endet — in vier Monaten, was ihn zu einem Notnagel macht und nicht zu einem Plan. Auf 28 zu bleiben ist nicht die konservative Wahl, als die es sich anfühlt; es heißt, eine nicht unterstützte Version zu betreiben, um sich die Unannehmlichkeit einer unterstützten zu ersparen.[branches]
| Ihre Situation | Die ehrliche Antwort |
|---|---|
| Compose-Bestand auf einem Host, selbst gebaute Images | Upgraden. Vorher ulimit -n im Container auditieren; das ist die einzige Änderung, die Sie wahrscheinlich überrascht. |
| CI-Runner mit Docker-in-Docker | Erst auf einem Runner testen. Das DinD-Service-Image auf die Major-Version des Daemons pinnen und beides gemeinsam ändern. |
| Sie hängen an einer selbst gehosteten Management-UI | Deren Wartungszustand prüfen, bevor Sie Dockers prüfen. Bei mehreren dieser Werkzeuge war die UI der Blocker, nicht die Engine. |
Sie pflegen eigene DOCKER-USER-Firewallregeln | Upgraden — unter dem Default-Backend iptables funktionieren sie weiter. Nicht auf nftables umstellen. |
| Sie verlassen sich auf getrennte Bridge-Netzwerke als Grenze | Den Netzwerkabschnitt vor dem Upgrade lesen, nicht danach. Das ist die Änderung mit echten Sicherheitsfolgen. |
| Sie bleiben zur Sicherheit auf 28 | docker-28.x ist upstream ungepflegt und außerhalb des Geltungsbereichs von Security Advisories. Das ist die riskantere Option, nicht die sicherere. |
- Inventarisieren Sie die Clients vor dem Daemon. Alles, was Ihren Docker-Socket hält, hat einen eigenen API-Client. Diese Liste — nicht die Engine-Version — entscheidet, ob dieses Upgrade ein Nicht-Ereignis oder ein Nachmittag wird.
- Machen Sie das ulimit-Audit zuerst, noch auf 28. Führen Sie heute
docker run --rm ubuntu:24.04 bash -c 'ulimit -n'aus und notieren Sie, welche Workloads das betrifft. Es sind die billigsten zehn Minuten auf dieser Seite und die Änderung, die am ehesten zwei Wochen später als mysteriöse Performance-Regression auftaucht. - Upgraden Sie in place. Installieren Sie nicht neu. Ein In-Place-Upgrade behält overlay2 und lässt Ihre Images sichtbar. Wenn Sie Hosts aus dem Configuration Management neu aufsetzen, entscheiden Sie bewusst, welchen image store der neu aufgesetzte Host verwenden soll, denn unter diesem Pfad hat sich der Default geändert.
- Prüfen Sie Ihre veröffentlichten Ports erneut. Die Isolation-Chains sind weg. Alles, was Sie auf
0.0.0.0veröffentlichen, ist jetzt in Fällen aus Containern anderer Netzwerke erreichbar, in denen es das vorher nicht war. Verschieben Sie das, was lokal bleiben sollte, auf127.0.0.1. - Lassen Sie nftables in Ruhe. Es ist experimentell, es entfernt die
DOCKER-USER-Chain, und im Swarm ist es nicht verfügbar. Es gibt bisher keinen Produktionsgrund, sich dafür zu entscheiden.
Wenn Sie darunter auch das Betriebssystem aktualisieren: Ubuntu 24.04 auf 26.04 auf Servern aktualisieren behandelt das Release, das cgroup v1 ganz entfernt und dabei sechs weitere Defaults austauscht. Falls Sie sich nach dieser Lektüre vor allem fragen, ob die Container-Plattform ihre Komplexität wert ist: wann man Kubernetes nicht einsetzen sollte begründet die kleinere Antwort, und langweilige Cloud-Architekturen ist das längere Argument dafür, Infrastruktur zu wählen, die sich absichtlich langsam ändert.
Häufig gestellte Fragen
Welche minimale API-Version verlangt Docker Engine 29?
Das hängt vom Patch-Release ab, und genau dieses Detail fehlt in den meisten Anleitungen. Engine 29.0.0 hob das Minimum von 1.24 auf 1.44 an, und 29.3.0 senkte es wieder auf 1.40. Der aktuelle Default im Upstream-Quellcode ist 1.40, 1.24 ist nur noch über ein explizites Override erreichbar. Prüfen Sie Ihren eigenen Daemon mit docker version — der Server-Abschnitt gibt die API-Version und das Minimum in derselben Zeile aus — statt einer Zahl aus einem Artikel von Ende 2025 zu vertrauen.
Wie behebe ich "client version is too old. Minimum supported API version is 1.44"?
Aktualisieren Sie den Client, und das ist fast immer ein Werkzeug und nicht die docker-CLI: Traefik 3.6.1, Portainer 2.33.5 LTS oder 2.36.0 STS, Testcontainers für Java 2.0.2, das Docker SDK für Python 7.1.0, CapRover 1.14.1. Wenn Sie es diese Woche wirklich nicht aktualisieren können, lässt sich dem Daemon beibringen, ältere Versionen zu akzeptieren: "min-api-version": "1.24" in /etc/docker/daemon.json (ein entsprechendes Kommandozeilen-Flag gibt es nicht) oder DOCKER_MIN_API_VERSION=1.24 in einem systemd-Drop-in. Upstream beschreibt beides als für Ausnahmefälle gedacht und kündigt an, ältere Versionen in einem künftigen Release zu entfernen, ohne ein Datum zu nennen.
Warum sind nach dem Update auf Docker 29 alle meine Images weg?
Sie sind mit ziemlicher Sicherheit nicht weg. Engine 29 macht den containerd image store nur bei Neuinstallationen zum Default, und die beiden Backends sehen die Inhalte des jeweils anderen nicht — Upstream beschreibt den Wechsel so, dass er Images und Container verbirgt, während die Daten auf der Platte bleiben. Nach einem In-Place-Upgrade der Pakete sollten Sie weiterhin auf overlay2 sein und alles sehen; wenn Sie gepurgt und neu installiert oder den Host aus dem Configuration Management neu aufgesetzt haben, haben Sie den neuen Default bekommen. Mit docker info -f '{{ .DriverStatus }}' sehen Sie, welches Backend aktiv ist. Für die alte Ansicht setzen Sie "features": {"containerd-snapshotter": false} in daemon.json und starten neu. Führen Sie kein docker system prune aus, solange Sie darüber im Unklaren sind.
Warum bekommen meine Container nach dem Upgrade "too many open files"?
Weil das Standard-Limit für File Descriptors in Containern von 1048576 auf 1024 gefallen ist. Docker Engine 29 liefert containerd 2.1.5 mit, das LimitNOFILE=infinity in seiner systemd-Unit nicht mehr setzt, sodass Container jetzt den gewöhnlichen Default von systemd erben. Für Build-Container hat Docker dieselbe Änderung in v25.0 vorgenommen, Version 29 dehnt sie auf alle Container aus. Beheben Sie es pro Workload mit --ulimit nofile=65535:65535 oder setzen Sie default-ulimits in daemon.json. Wählen Sie eine Zahl, die Sie begründen können, statt 1048576 wiederherzustellen — der alte Wert ist genau das, wovon die Änderung wegwollte.
Bricht Docker 29 meine DOCKER-USER-Firewallregeln?
Nicht unter dem Default-Backend iptables, wo DOCKER-USER weiterhin existiert und weiterhin funktioniert. Die Chain verschwindet nur, wenn Sie bewusst das experimentelle nftables-Backend aktivieren; dort sagt Upstream klar, dass es keine DOCKER-USER-Chain gibt und dass Ihre Regeln nicht migriert werden. Wie sie aufhören zu greifen, hat eine Tücke: Beim Umschalten eines bestehenden Hosts bleibt der alte Sprung aus der FORWARD-Chain bestehen, die Regeln feuern also weiter, bis dieser Sprung entfernt wird oder der Host neu startet, während ein frisch installierter nftables-Host sie von Anfang an ignoriert. Was sich für alle geändert hat, ist die Entfernung der Chains DOCKER-ISOLATION-STAGE-1 und DOCKER-ISOLATION-STAGE-2, was ausweitet, was Container in einem Netzwerk in einem anderen erreichen können. Das ist eine Änderung der Erreichbarkeit und keine Firewall-Änderung, und es ist der Teil dieses Releases, der ein Audit verdient.
Sollte ich das nftables-Backend in Docker 29 aktivieren?
Auf einem Produktionshost nicht. Es ist ausdrücklich experimentell — Upstream warnt, dass Konfigurationsoptionen, Verhalten und Implementierung sich allesamt ändern können —, es lässt sich nicht aktivieren, solange der Daemon im Swarm-Modus läuft, und es entfernt die DOCKER-USER-Chain. Auf einer modernen Distribution werden Ihre iptables-Regeln bereits von der nftables-Kernel-Maschinerie ausgeführt, der praktische Gewinn ist heute also gering. Wenn Sie es testen wollen, tun Sie es auf einem Host, auf dem Sie sich bei der Firewall irren dürfen.
Ist es sicher, auf Docker Engine 28 zu bleiben?
Weniger sicher als ein Upgrade, was dem Gefühl genau zuwiderläuft. Die Branch-Tabelle von Upstream markiert docker-28.x als unmaintained und definiert das als nicht mehr aktiv entwickelt, keine Beiträge annehmend und außerhalb des Geltungsbereichs von Security Advisories. Gepflegt werden nur docker-29.x und 25.0, und 25.0 wird für zwei bestimmte Downstream-Distributionen am Leben gehalten, mit einem voraussichtlichen Wartungsende im Dezember 2026. Zwei Wochen auf 28 zu pinnen, während Sie einen Client reparieren, ist vernünftig; darauf als Policy zu pinnen heißt, eine Version zu betreiben, die keine Sicherheitsfixes mehr erhält.
Kann ich von Docker 29 auf 28 zurückrollen?
Ja — installieren Sie die konkrete ältere Version mit Ihrem Paketmanager, genau so, wie es die Installationsdokumentation beschreibt. Drei Dinge macht das Rollback nicht rückgängig. Wenn der Daemon auf den containerd image store umgestellt wurde, sieht ein 28.x-Daemon diese Images nicht; sie liegen auf der Platte, aber nicht im Graph Driver, und eine Konvertierung gibt es nicht. Ein Netzwerk, das auf 29 durch Anforderung einer Präfixlänge aus den Default Address Pools angelegt wurde, ist auf einem älteren Daemon unbrauchbar und muss gelöscht und neu erstellt werden. Und rootless-Installationen erhalten ab 29.5 slirp4netns nicht mehr über die Docker-Pakete, installieren Sie es also aus Ihrer Distribution nach, wenn Sie auf einen Build zurückgehen, der es erwartet.
Unterstützt Docker Engine 29 noch cgroup v1?
Ja. Version 29 erklärt es für deprecated, aber Upstream sagt Support bis Mai 2029 zu und weist darauf hin, dass selbst dann mindestens ein gepflegter Branch die Unterstützung behält. Wegen Docker hört dieses Jahr nichts auf Ihren Servern auf zu funktionieren. Der Druck kommt aus der anderen Richtung: systemd hat die Legacy- und Hybrid-Hierarchien upstream entfernt, Ihre Linux-Distribution wird cgroup v1 also deutlich vor Docker fallen lassen. Ein Host, der noch mit systemd.unified_cgroup_hierarchy=0 bootet, sollte jetzt aufgeräumt werden, solange es eine einzeilige Änderung ist und kein Upgrade-Blocker.
Quellen
Für alles, was die Engine selbst betrifft, sind Dockers Release Notes, die Dokumentation und der moby-Quellbaum maßgeblich; für die Kompatibilitätsaussagen die Tracker der betroffenen Projekte. Zwei ehrliche Einschränkungen zu dieser Liste. Die Version, in der ein Drittanbieter-Werkzeug jeweils repariert wurde, stammt in der Regel aus den Release Notes des betreffenden Projekts und nicht aus dem hier verlinkten Bug-Report — folgen Sie also dem Projekt, wenn Sie es bestätigen müssen. Und wo Upstream kein Datum und keine Version veröffentlicht — die Entfernung des API-Version-Overrides ist der bemerkenswerte Fall —, steht das hier so, statt zu raten.
- Docker — Docker Engine v29 release notes: the authoritative changelog for every behaviour change described here, including the API floor, the containerd packaging bump and the networking rules
- Docker — Docker Engine v29: Foundational Updates for the Future; the announcement post, and the source for the DOCKER_MIN_API_VERSION workaround
- Docker — Engine API reference: the version matrix mapping each Engine release to its maximum and minimum API version, which is the only reliable way to know what your daemon will accept
- Docker — Deprecated Engine features: the table that carries the cgroup v1 deprecation and its May 2029 support horizon
- Docker — containerd image store: fresh installs versus upgrades, how to check which backend is active, and the fact that switching hides rather than deletes content
- Docker — Docker and nftables: the experimental backend, the tables it creates, and the statement that there is no DOCKER-USER chain
- Docker — Packet filtering and firewalls: the iptables model, the DOCKER-USER chain and the gateway modes referenced in the networking section
- Docker — dockerd reference: daemon.json keys, default-ulimits, storage-driver and the feature flags used in this article
- Docker — Configure the daemon, including the data directory location and why containerd's storage is configured separately
- Docker — Install Docker Engine on Ubuntu: the apt repository, the exact package set and the documented way to install a specific version
- Docker — Install Docker Engine on RHEL: the dnf repository and the equivalent version-pinning procedure
- Docker — Release lifecycle: the stages Docker applies to features and the notice it commits to before retiring them
- moby/moby — Branches and tags: the branch maintenance table showing docker-29.x maintained and docker-28.x unmaintained, and the definition of unmaintained
- moby/moby — daemon/config/config.go: the MaxAPIVersion, defaultMinAPIVersion and MinAPIVersion constants, and the comment describing min-api-version as an exceptional-case option
- moby/moby #51186 — daemon: raise minimum API version to v1.44, the change that shipped in 29.0.0
- moby/moby #52067 — lower minimum API version from v1.44 to v1.40, the change that shipped in 29.3.0 and that most published coverage predates
- moby/moby #49981 — the bridge iptables rework that removed the DOCKER-ISOLATION-STAGE-1 and DOCKER-ISOLATION-STAGE-2 chains
- moby/moby #50719 — legacy link environment variables are no longer added automatically, with the DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS escape hatch
- moby/moby #50476 — the --bridge-accept-fwmark daemon option that lets a firewall mark override Docker's drop rules
- moby/moby #50114 — requesting a prefix size from the default address pools, and the warning that such networks are unusable after a downgrade
- moby/moby #51485 — LimitNOFILE is silently changed to the host soft limit with the new containerd: the report, the maintainer's explanation and the resulting release-note text
- moby/moby #51111 — the cgroup v1 deprecation tracking issue for Docker Engine
- moby/moby #47377 — the userns-remap bug that keeps the containerd image store unavailable when user-namespace remapping is enabled
- containerd v2.1.5 — the runtime version packaged with Docker Engine 29, and the origin of the changed LimitNOFILE default
- Linux kernel documentation — Control Group v2, the hierarchy Docker will require once cgroup v1 support ends
- systemd v258 release notes — the removal of the legacy and hybrid cgroup hierarchies upstream, which is why your Linux distribution will drop cgroup v1 well before Docker does
- traefik #12253 — the Docker provider's hardcoded API version 1.24 against a v29 daemon, fixed by moving to version negotiation
- portainer #12925 — local Docker environment unreachable on Engine 29, the primary report carrying the maintainers' fixed-version announcement; builds before 2.33.5 capped their Docker client at API 1.41 and checked the daemon's reported minimum strictly
- Portainer — Docker v29, and the fall-out: the most complete public inventory of management tools broken by the API floor and their fixed versions
- testcontainers-java #11235 — docker-java's default API version rejected by Engine 29, and the properties file workaround
- GitLab Runner #39129 — API version mismatches between the runner, the job image and the Docker-in-Docker service on Engine 29
- GitLab Runner #39131 — service health checks fail on Docker 29 because the wait-for-service helper relied on the legacy link environment variables that are no longer injected
- watchtower #2122 — a tool pinned to API version 1.25 whose repository has since been archived read-only, and what that means for anything holding your Docker socket that nobody maintains
- docker/docker-py — the Docker SDK for Python, whose DEFAULT_DOCKER_API_VERSION moved from 1.41 in 6.1.3 to 1.44 in 7.1.0
- community.docker #1185 — KeyError: 'ApiVersion' from the docker version JSON shape change in 29.0.0, corrected in 29.0.1
- JetBrains IJPL-217878 — the IDE Docker plugin rejected by a v29 daemon until the plugin build shipped with the 2025.3 releases
- docker/compose #13371 — the absence of a published Compose-to-Engine API compatibility matrix, and why old Compose builds fail against v29
War das hilfreich?