تجاوز إلى المحتوى
← المدونة

Docker Engine 29: ما الذي يتعطّل

أرضية الـ API تحرّكت مرتين، ومخزن الصور لا يتبدّل إلا في التثبيت النظيف، وحدّ واصفات الملفات داخل كل حاوية هبط بصمت من 1048576 إلى 1024. تسعة أشهر من التبعات، مراجَعة مقابل المصدر الرسمي.

·قراءة 17 دقيقة
  • Docker
  • containerd
  • Linux
  • DevOps

صدر Docker Engine 29 في نوفمبر 2025 وعطّل قدرًا لافتًا من البرمجيات التي كانت تعمل بلا مشاكل منذ سنوات. وبعد تسعة أشهر، الجزء المثير للاهتمام ليس العطل نفسه — بل أن معظم ما كُتب عنه في الأسبوعين الأولين صار اليوم خاطئًا، لأن التغيير الذي وثّقه الجميع جرى التراجع عن جزء منه بعد ثلاثة إصدارات فرعية. إن كنت تُرقّي خادم Linux هذا الشهر، فأنت تقرأ نصائح كُتبت لنسخة من Docker 29 لم تعد موجودة.

مخطط لتغييرات Docker Engine 29 التي تكسر الأشياء: رفع الحد الأدنى لإصدار الـ API إلى 1.44 ثم خفضه إلى 1.40، ومخزن الصور containerd كإعداد افتراضي في التثبيت النظيف، وهبوط حدّ nofile داخل الحاوية من 1048576 إلى 1024، وإلغاء متغيّرات link القديمة، وحذف سلاسل العزل في iptables.
خمسة تغييرات. الأول وحده يقع تحت العنوان الذي يقول "breaking changes" في ملاحظات الإصدار؛ أما الأربعة الأخرى فموزّعة بين التحزيم والشبكات وصندوق تنبيه.

هذا ما يتعطّل فعليًا، بالترتيب الذي يُرجَّح أن تصادفه به، مع الحل والمصدر الرسمي لكل حالة. المقال مكتوب لمن يشغّل Docker Engine على خوادم Linux — لا Docker Desktop، حيث تختلف إجابات عدد من هذه الأسئلة. أوامر التشخيص هنا لا تفعل شيئًا سوى قراءة الحالة؛ وكل ما يغيّر النظام هو تعديل إعداد أو تثبيت أو إعادة تشغيل، وهو بيّن بذاته. وحيثما لم ينشر المصدر الرسمي تاريخًا أو إصدارًا، يقول المقال ذلك صراحةً بدل اختلاق رقم.

ابدأ من العَرَض، لا من سجل التغييرات

لا أحد تقريبًا يصل إلى هذه الصفحة من سجل التغييرات. الناس يصلون من نص خطأ، أو من حاوية ترفض الإقلاع، أو من لوحة تحكّم تقول إن البيئة غير قابلة للوصول. لذلك يأتي هذا الجدول أولًا: جِد العَرَض الذي تراه، ثم اقرأ القسم الذي يشرحه.[rel29]

ما تراهما هو فعليًاأين يُصلَح
client version 1.24 is too old. Minimum supported API version is 1.44عميل أقدم من أرضية API الخاصة بالخادوم. تحرّكت الأرضية في 29.0.0 — وعادت في 29.3.0.حدّث العميل. التجاوز على جانب الخادوم جسر مؤقت لا إصلاح.
client version 1.52 is too new. Maximum supported API version is 1.44الصورة المعكوسة: عميل جديد أمام خادوم قديم. وهي دائمًا تقريبًا خدمة Docker-in-Docker مثبّتة على إصدار في الـ CI.ثبّت صورة DinD على الإصدار الرئيسي نفسه للخادوم، وحرّكهما معًا.
docker images فارغ بعد تثبيت 29مخزن الصور containerd هو الافتراضي في التثبيت النظيف. محتوى overlay2 لديك مخفي لا محذوف.أعد التبديل إلى الواجهة الخلفية السابقة، أو صدّر وأعد الاستيراد عن قصد.
الحاويات تصطدم بسقوف اتصال لم تكن موجودة من قبلحدّ nofile الافتراضي داخل الحاويات هبط من 1048576 إلى 1024.--ulimit لكل حاوية، أو default-ulimits على الخادوم.
مهام الـ CI تفشل عند فحص سلامة خدمة؛ No HOST or PORT foundمتغيّرات بيئة link القديمة لم تعد تُحقن، فالمساعد الذي ينتظر الخدمة لا يراها أبدًا.شبكة معرَّفة من المستخدم مع DNS. المتغيّر الاستثنائي مؤقت.
حاوية تصل إلى منفذ منشور لا ينبغي أن تصل إليهسلسلتا DOCKER-ISOLATION-STAGE-1 و-STAGE-2 حُذفتا في 29.0.انشر على 127.0.0.1 بدل عنوان البدل الشامل.
قواعد جدارك الناري في DOCKER-USER توقّفت عن السريانلا يحدث هذا إلا إن فعّلت واجهة nftables الخلفية، وهي تجريبية واختيارية.أعد التبديل، أو اكتب جدول nftables خاصًا بك بأولوية أدنى.
بناء Go يفشل على github.com/docker/dockerانتقلت الوحدة إلى github.com/moby/moby/client و.../api.أعد كتابة الاستيرادات؛ فقد تغيّر شكل واجهة العميل في الوقت نفسه.
# 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 2029

ثلاثة من تلك الصفوف هي التغيير الأساسي نفسه منظورًا إليه من زوايا مختلفة، واثنان منها غير موثّقين في أي مكان يخطر لك أن تبحث فيه. وقبل أن تمسّ أي شيء، ثبّت الحقائق الثلاث التي تحدّد أيًّا منها ينطبق عليك.

أرضية الـ API تحرّكت مرتين، والرقم الذي تقرأه غالبًا خطأ

هذا هو التغيير الشهير. رفع الإصدار 29.0.0 الحدَّ الأدنى لإصدار Engine API الذي يقبل الخادوم التحدّث به إلى 1.44، أي ما يقابل Docker 25.0. أما الأرضية السابقة فكانت 1.24 — وهي نفسها لم تُستحدث إلا في Docker 25.0، لتحلّ محل أرضية 1.12 التي ظلّت قائمة منذ 2016 — أي أن هذا هو التضييق الثاني في عامين، والأول الذي انتبه إليه أحد. أي عميل ثبّت إصدارًا أقدم في شفرته، أو لم يتعلّم التفاوض أصلًا، توقّف عن العمل لحظة إعادة تشغيل الخادوم. لهذا أسقطت الترقية في الأسبوع نفسه Traefik وPortainer وTestcontainers وWatchtower ونصف دزينة من لوحات التحكّم المستضافة ذاتيًا وعددًا لا بأس به من مسارات الـ CI.[pr51186][blog29]

وهنا الجزء الذي لا يكاد يرد في أي مقال منشور. في 29.3.0، وهو إصدار فرعي صدر في مارس 2026، خفّض المصدر الرسمي الأرضية مجددًا — من 1.44 إلى 1.40، أي ما يقابل Docker 19.03. القيمة الافتراضية في الشفرة الحالية هي 1.40، ويبقى 1.24 متاحًا فقط عبر تجاوز صريح. أي أن عميلًا على API 1.41 أو 1.43 كان مرفوضًا في ديسمبر يعمل اليوم، وأي دليل استكشاف أخطاء يخبرك بأن الحد الأدنى هو 1.44 إنما يصف أول ثلاثة إصدارات فرعية من سلسلة تجاوزتها بمراحل. افحص الخادوم عندك بدل الاعتماد على ذاكرة الإنترنت عنه — فجدول مقابلة الإصدارات بإصدارات الـ API منشور، وهو الرواية الوحيدة من هذه القصة التي تبقى صحيحة.[pr52067][config][apimatrix]

إصدار الخادومالحد الأدنى لإصدار APIما الذي يرفضه
25.0 – 28.x1.24لا شيء تقريبًا. والمحرّكات الأقدم كانت أرضيتها 1.12، ولم تتغيّر منذ 2016.
29.0.0 – 29.2.x1.44كل عميل أقدم من Docker 25.0. هذه هي موجة الأعطال التي كتب عنها الجميع.
29.3.0 وما بعده1.40العملاء الأقدم من Docker 19.03. تخفيف ذو معنى، ونادرًا ما يُذكر.
أي إصدار، مع التجاوزنزولًا إلى 1.24لا خيار له في سطر الأوامر: إما مفتاح daemon.json أو DOCKER_MIN_API_VERSION. ويصفه المصدر الرسمي بأنه للحالات الاستثنائية فقط، بلا تاريخ حذف منشور.
# 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.

تنبيه واحد بخصوص التجاوز، لأنه من نوع الإعدادات التي تعمّر أطول من الحادثة التي أوجدتها. يصف المصدر الرسمي إصدارات الـ API الأقدم بأنها مهملة وستُحذف في إصدار مقبل، ويصف كلًّا من متغيّر البيئة ومفتاح الإعداد بأنهما للحالات الاستثنائية فقط. ولم يُنشر تاريخ للحذف. وسياسة Docker نفسها هي الإبقاء على أي ميزة مهملة لإصدار مستقر واحد على الأقل، والسعي إلى إشعار بأشهر قبل أي إحالة إلى التقاعد، فالأمر إذن لن يختفي بين ليلة وضحاها — لكن "إصدارًا واحدًا على الأقل" أرضية لا خطة. استخدمه لتجاوز أسبوع، لا لتأسيس سياسة.[config][lifecycle]

«اختفت كل صوري» حالة إعادة تثبيت، لا ترقية

المشكلة الثانية من حيث كثرة البلاغات هي الأكثر إثارةً للفزع والأقل خطورة: تثبّت Docker 29، وتنفّذ docker images، فتجد القائمة فارغة. لم يُحذف شيء. يجعل الإصدار 29 مخزنَ صور containerd — أي الـ containerd image store — هو الواجهة الخلفية الافتراضية، لكن — وهذه هي الجملة التي تحلّ كل البلاغات تقريبًا — في التثبيت النظيف فقط. أما ترقية الحزم في مكانها فتُبقي مشغّل الرسم overlay2 وتُبقي صورك ظاهرة. وإزالة الحزم بالكامل ثم إعادة تثبيتها تُحسب تثبيتًا نظيفًا، وكذلك إعادة بناء المضيف من أدوات إدارة الإعدادات، وهي الطريقة التي يصادف بها معظم الناس هذا الأمر دون أن يظنّوا أنهم فعلوا شيئًا غير معتاد.[cstore]

السؤالoverlay2 (مشغّل الرسم)لقطات containerd
متى تحصل عليهأي ترقية من 28 أو أقدم تُبقيهالتثبيت النظيف للإصدار 29.0+، إلا مع userns-remap
ما يذكره docker infoStorage Driver: overlay2Storage Driver: overlayfs مع driver-type بقيمة io.containerd.snapshotter.v1
أين يقيم المحتوىتحت data-root، وعادةً /var/lib/dockerفي تخزين containerd الخاص، الذي لا ينقله data-root
حجم القرص المستهلكطبقات غير مضغوطة فقطمضغوطة وغير مضغوطة معًا، فالحجم أكبر بوضوح للصور نفسها
الصور متعددة المنصات والإثباتاتغير مدعومةمدعومة — وهي السبب الفعلي لتغيير الافتراضي
التبديل بينهمايُخفي صور وحاويات الواجهة الخلفية الأخرىوالعكس بالمثل. لا شيء يُحذف؛ ولا توجد تحويل في المكان
# "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.

الواجهتان الخلفيتان لا ترى إحداهما محتوى الأخرى. والمصدر الرسمي صريح في أن التبديل "temporarily hides images and containers created with the other backend" وأن "your data remains on disk". فإن كنت قد بدّلت للتو ووجدت القائمة فارغة، فلا تبدأ بتنفيذ prune لاستعادة مساحة — ستكون تحذف النصف الذي تراه بينما تدفع ثمن النصف الذي لا تراه.

تظهر نتيجتان بعد أسابيع لا في اليوم نفسه. مخزن containerd يحتفظ بالطبقات مضغوطةً وغير مضغوطة معًا، فتشغل الصور ذاتها مساحة قرص أكبر بفارق ملموس عمّا كانت تشغله تحت overlay2. كما أن data-root في daemon.json لا ينقل محتوى containerd: فإن كنت قد وضعت /var/lib/docker بعناية على وحدة تخزين مستقلة، فإن تخزين containerd يُضبط بشكل منفصل وسيملأ نظام الملفات الجذري بدلًا من ذلك. وهناك حالة واحدة لا ينطبق فيها الإعداد الافتراضي الجديد أصلًا — المضيفات التي تستخدم إعادة تعيين مجالات أسماء المستخدمين مستثناة، بسبب عِلّة مفتوحة لا بسبب قرار سياسة. وكن صريحًا مع نفسك بشأن ما يشتريه لك التبديل رجوعًا: مشغّلات الرسم القديمة نفسها مهملة، وقد قالت Docker إن هذه الواجهة الخلفية ستُحذف في إصدار مقبل. العودة إلى overlay2 تأجيل للتنفيذ لا إلغاء له.[daemon][iss47377]

حدّ واصفات الملفات في كل حاوية هبط إلى 1024

هذا هو التغيير الذي أراهن على أنه الأكثر عرضةً للتشخيص الخاطئ، لأنه ليس في قسم التغييرات الكاسرة إطلاقًا. إنه بند فرعي تحت ترقية إصدار containerd. يحزم Docker Engine 29.0.0 نسخة containerd 2.1.5، التي توقّفت عن ضبط LimitNOFILE=infinity في وحدة systemd الخاصة بها — فهبط الحدّ المرن الافتراضي للملفات المفتوحة داخل كل حاوية من 1048576 إلى 1024. وإصدارات 29.x اللاحقة تحمل نسخًا أحدث من containerd، وكلها تُبقي على السلوك الجديد.[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.

المنطق وراء التغيير سليم، والمشرفون يشرحونه بوضوح: قيمة غير محدودة عمليًا، مقترنةً بخصوصية في زمن تشغيل Go، قد ترفع الحدّ المرن إلى الحدّ الصارم، فتلتهم الآلةَ برمجياتٌ تحسب أحجام مخازنها المؤقتة من ulimit -n — وMySQL هو المثال الدائم. وقد أجرى Docker التغيير نفسه على حاويات البناء منذ الإصدار v25.0؛ والإصدار 29 يوسّعه ليشمل الجميع. المشكلة ليست في التغيير، بل في أن 1024 سقف حقيقي، وأن الأعطال الناتجة عنه لا تذكر واصفات الملفات أبدًا تقريبًا. ستحصل على عامل Nginx يرفض الاتصالات عند رقم يبدو اعتباطيًا، وعلى selector في JVM يفشل تحت الحمل، وعلى تجمّع اتصالات يتوقّف عند القدر نفسه من حركة المرور الذي احتمله الأسبوع الماضي. إن كنت قد رقّيت ثم صار شيء أبطأ أو أكثر تقلّبًا تحت الحمل دون أن تتغيّر أي شفرة، فافحص هذا أولًا.[rel29]

توقّف Docker 29 عن حقن متغيّرات بيئة link القديمة — DB_PORT_5432_TCP_ADDR وعائلتها — داخل الحاويات. كانت مهملة منذ سنوات، والبديل، أي DNS على شبكة معرَّفة من المستخدم، يعمل منذ 2016. المشكلة أن كثيرًا من سكربتات نقطة الدخول في الحاويات ومن مساعدات الـ CI ما زالت تقرأها، والعطل الناتج لا يذكر Docker من قريب أو بعيد. والحالة النموذجية هي GitLab Runner: حاوية المساعد التي تنتظر إقلاع مدخلة services: تخرج بـ FATAL: No HOST or PORT found، فتفشل المهمة عند فحص السلامة بينما حاوية الخدمة نفسها تعمل على ما يرام.[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".

لهذه المسألة حدّ حقيقي: حتى لحظة كتابة هذا المقال، ما زالت مسألة GitLab Runner المقابلة مفتوحة، والحل البديل الموثّق هو إما ضبط متغيّر البيئة الاستثنائي على الخادوم أو تثبيت إصدار Docker الخاص بالـ runner. إن كانت منظومة الـ CI لديك تشغّل حاويات خدمة، فاختبر هذا على runner واحد قبل تعميم الترقية على الأسطول — لا لأن إصلاحه صعب، بل لأن العطل يظهر في كل المهام دفعةً واحدة ويبدو كانقطاع في البنية التحتية لا كتغيير في الإعدادات.

حذف سلاسل العزل يعني تغيّرًا في نطاق الوصول

هذا هو التغيير الأقل ضجيجًا والأكثر أثرًا. أعاد الإصدار 29 صياغة قواعد iptables لشبكات الجسر وحذف سلسلتَي DOCKER-ISOLATION-STAGE-1 وDOCKER-ISOLATION-STAGE-2 بالكامل. تذكر ملاحظات الإصدار الآثار بصراحة، وتستحق قراءتين إن كنت يومًا قد عاملت الشبكات الجسرية المنفصلة كحدّ أمني: صار بإمكان الحاويات الآن الوصول إلى المنافذ المنشورة على عناوين المضيف من قِبل حاويات في شبكات أخرى حين لا يكون وكيل مساحة المستخدم قيد التشغيل، وإلى المنافذ على عناوين الحاويات في شبكات أخرى عند استخدام وضع البوابة nat-unprotected.[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".
السلوكحتى 28.xمن 29.0ما العمل
سلاسل عزل شبكات الجسرDOCKER-ISOLATION-STAGE-1 و-STAGE-2محذوفةأعد فحص أي شيء منشور على عنوان بدل شامل.
متغيّرات بيئة link القديمةتُحقن تلقائيًالا تُحقناستخدم DNS على شبكة معرَّفة من المستخدم.
سلسلة DOCKER-USERموجودةموجودة تحت iptables، غائبة تحت nftablesلا تفعّل واجهة nftables الخلفية إن كنت تعتمد عليها.
قاعدة mangle لمجموع تحقّق SCTPفقط مع DOCKER_IPTABLES_SCTP_CHECKSUM=1محذوفةلم يعد لهذا المتغيّر أي أثر على الإطلاق.
البوابة الافتراضية لـ macvlan وipvlan-l2تُستنتجفقط إذا كان --gateway في إعداد IPAMحدّد البوابة صراحةً في تعريف الشبكة.
شبكات overlay المشفّرةمعطّلة على 28.2.2 و25.0.13–14معطّلة من 29.0.0 حتى 29.2.0يُصلحها 29.2.1، لكن عقدة مُصلَحة لا تستطيع حمل حركة المرور إلى عقدة غير مُصلَحة. انقل عنقود Swarm كاملًا عن البُنى المتأثرة دفعةً واحدة.

لا شيء في الترقية يخبرك بأن هذا حدث، ولا شيء في مراقبتك سيفعل — فهو توسيع لنطاق الوصول لا خطأ. والجواب الدائم ليس إعادة إنشاء السلاسل، بل التوقّف عن النشر إلى عنوان البدل الشامل حين تقصد localhost، وهي الصيغة الصحيحة أصلًا ولا تعتمد على وجود أي سلسلة. وما دمت هناك، فثمة خطر واحد يتعلّق بالتراجع يستحق التدوين: الشبكة المُنشأة على 29 بطلب حجم بادئة من تجمّعات العناوين الافتراضية تصبح غير قابلة للاستخدام على خادوم أقدم ويجب حذفها وإعادة إنشائها، فسجّل تعريفات شبكاتك قبل أن تبدأ لا بعدها.[pr50114]

nftables حقيقي وتجريبي وبلا سلسلة DOCKER-USER

قدّم الإصدار 29 أيضًا واجهة خلفية بـ nftables، ويجدر التدقيق في وضعها لأن التغطية الإعلامية لم تدقّق: إنها تجريبية واختيارية، والافتراضي يظل iptables، ولا يمكن تفعيلها إطلاقًا ما دام الخادوم في وضع Swarm. ويصرّح المصدر الرسمي بأن خيارات الإعداد والسلوك والتنفيذ قد تتغيّر جميعها. وعلى توزيعة حديثة، فإن قواعد iptables عندك تُنفَّذ عادةً عبر آلية nftables في النواة على أي حال، فالتبديل لا يشتري لك اليوم إلا القليل جدًا. سبب معرفتك بها الآن هو أثرها على جدارك الناري: في تنفيذ Docker بـ nftables لا توجد سلسلة DOCKER-USER، وقواعدك لا تُرحَّل إلى الجداول الجديدة. أما هل تظل تعمل فيتوقّف على تاريخ المضيف — فتبديل مضيف قائم يترك القفزة القديمة من FORWARD في مكانها، فتظل القواعد تُطلَق حتى تزول تلك القفزة أو يُعاد إقلاع المضيف، بينما مضيف بدأ حياته على nftables لم تكن لديه تلك القفزة قط فيتجاهلها بصمت. جهازان بإعداد متطابق، وجداران ناريان مختلفان.[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 mask

github.com/docker/docker لم يعد مسار الاستيراد لديك

إن كنت تستهلك واجهة Docker البرمجية من Go، فالإصدار 29 إعادة كتابة لا مجرد ترقية رقم، وهو التغيير الوحيد في هذه الصفحة الذي لا يمكن الالتفاف عليه بمفتاح إعداد. الوحدة github.com/docker/docker مهملة لصالح github.com/moby/moby/client وgithub.com/moby/moby/api؛ والوحدة الأم صارت صراحةً تفصيلًا داخليًا في التنفيذ، والإصدارات تُوسم ببادئة docker-. وفوق النقل، تغيّر شكل واجهة العميل نفسها:[rel29]

  • بُنى الخيارات تحلّ محل الوسائط الموضعية في عمليات الصور والإعدادات والتنظيف. تعديل ميكانيكي لكنه واسع — يمسّ كل موضع استدعاء تقريبًا في أي تكامل نموذجي.
  • القيم المُعادة صارت مغلَّفة في بُنى. لم تعد ImageInspect وImageHistory وImageLoad وImageSave تعيد ما كانت تعيده، وصارت ImagePull وImagePush تعيد كائنات تكشف مكرّرات رسائل.
  • المرشّحات انتقلت. صار للعميل نوع مرشّحات خاص به، فأي شفرة تستورد حزمة filters القديمة تحتاج إعادة هيكلة لا مجرد إعادة توجيه.
  • العناوين صارت مُنمَّطة. عناوين IP والشبكات الفرعية صارت netip.Addr وnetip.Prefix بدل السلاسل النصية وnet.IPNet، وهو تحسين حقيقي وصباح عمل حقيقي.
  • اختفت client.ImageCreate، واستُبدل بها ImagePull أو ImageImport بحسب ما كنت تفعله بها فعلًا.

إهمال cgroup v1، ومعه تاريخ فعلي

يُهمل Docker 29 دعم cgroup v1 — وخلافًا للمعتاد في حالات الإهمال، يأتي هذا مصحوبًا بتاريخ. يذكر المصدر الرسمي أن الدعم مستمر حتى مايو 2029، وأن الإصدار الأخير في مايو 2029 قد لا يدعم cgroup v1 بنفسه لكن فرعًا مصانًا واحدًا على الأقل سيدعمه. هذه مهلة سخية فعلًا، ومعناها أن لا شيء على خوادمك يتوقّف هذا العام بسبب ذلك.[deprecated][iss51111]

ومع ذلك يستحق الأمر تحرّكًا مبكرًا، لسبب لا علاقة له بجدول Docker الزمني: توزيعتك ستصل إلى هناك أولًا. فقد حذف systemd التسلسلين القديم والهجين في الإصدار v258، ما يعني أن مضيفًا ما زال يُقلع بـ systemd.unified_cgroup_hierarchy=0 — وهو عادةً معامل أضافه أحدهم قبل سنوات لإرضاء زمن تشغيل قديم ثم لم يُزله أبدًا — سيصطدم بالجدار على مستوى نظام التشغيل قبل 2029 بوقت طويل. المعامل سهل الإيجاد وسهل الإزالة عادةً؛ والجزء المزعج هو اكتشافه أثناء نافذة صيانة لا في يوم ثلاثاء عادي.[systemd258][cgroupv2]

المنظومة: ما الذي تعطّل، والإصدار الذي أصلحه

جدول التوافق هو الجزء الموثّق جيدًا فعلًا من هذه القصة، ويعود الفضل في ذلك إلى حدّ بعيد إلى Portainer التي وثّقت التبعات بالتفصيل أثناء وقوعها. وقد أُصلح معظمها خلال أسابيع. القيمة في قراءة القائمة الآن ليست في الإصلاحات، بل في أن تلاحظ أيًّا منها تشغّل أنت.[portblog]

الأداةما الذي تعطّلأُصلح في
Traefikمزوّد Docker كان يثبّت إصدار API 1.24 في شفرته، فلم تُضبط أي مسارات إطلاقًا.3.6.1 — استُبدل التفاوض بالإصدار المثبّت.
Portainerكان العميل المضمَّن مسقوفًا عند API 1.41 دون تفاوض، وتحقّق صارم من الإصدار الأدنى رفض الاتصال؛ فظهرت البيئات كغير قابلة للوصول.2.33.5 LTS / 2.36.0 STS.
Testcontainers for Javaكانت docker-java تعتمد API 1.32 افتراضيًا؛ ففشلت الاختبارات في إيجاد بيئة Docker صالحة.2.0.2، وتأتي بنسخة docker-java تتفاوض.
Docker SDK for Pythonكانت DEFAULT_DOCKER_API_VERSION تساوي 1.41 في 6.1.3.7.1.0 (1.44). والأفضل التفاوض بدل التثبيت.
GitLab Runnerعدم تطابق الإصدارات بين الـ runner وصورة المهمة وخدمة DinD؛ وبشكل منفصل، تفشل فحوص سلامة الخدمات بـ No HOST or PORT found.ثبّت صورة DinD أو اضبط إصدار API. ومسألة فحص السلامة كانت ما تزال مفتوحة وقت الكتابة.
بيئات JetBrainsرفض خادوم v29 إضافة Docker.بنية الإضافة 253.28294.x، شُحنت في 2025.2.5 وفي إصدارات 2025.3.
Watchtower (containrrr)مثبّتة على API 1.25، وفي حلقة إعادة تشغيل أمام خادوم v29.لا إصلاح رسمي. أُرشِف المستودع للقراءة فقط في ديسمبر 2025.
CapRoverdocker-modem مثبّتة على API 1.43 — أقل بإصدار واحد من أرضية 29.0.1.14.1.
Ansible community.dockerKeyError: 'ApiVersion' بسبب تغيّر حالة الأحرف في JSON الخاص بـ docker version.أعاد Engine 29.0.1 أسماء الحقول السابقة.
Docker Composeالبُنى القديمة جدًا تقع تحت أرضية الـ API؛ ولا مصفوفة توافق منشورة.ثبّت docker-compose-plugin الحالي من المستودع نفسه الذي يأتي منه المحرّك.

النمط متسق: كل ما تعطّل كان يحمل في مكان ما إصدار API مثبّتًا في الشفرة بدل أن يتفاوض عليه، وكل ما أُصلح أُصلح بإضافة التفاوض. وPortainer هي أوضح مثال على ذلك: عميلها المضمَّن كان مسقوفًا عند API 1.41 دون أي تفاوض، وتحقّق صارم من الحدِّ الأدنى الذي يعلنه الخادوم رفض الاتصال رفضًا قاطعًا، على خادوم كان مستعدًا تمامًا للتحدث إلى عميل يسأل كما ينبغي. وGitLab Runner مزعجة لسبب مختلف — لأن الـ runner وصورة المهمة وخدمة Docker-in-Docker يحمل كل منها عميله الخاص، وأي اثنين منها قد ينتهيان على جانبين متقابلين من الأرضية. والقاعدة العملية هناك هي تثبيت صورة خدمة DinD على الإصدار الرئيسي نفسه للخادوم الذي يشغّلها، وتغييرهما معًا.[traefik][testcontainers][glrunner]

بقية القائمة تدقيق لا بأس به لسلسلة توريدك. وWatchtower هي الحالة المعبّرة: مثبّتة على API 1.25، وبلا إصلاح رسمي، وقد أُرشِف مستودعها للقراءة فقط في ديسمبر 2025 — أي أن عملية تعمل دون إشراف ولها وصول دائم إلى مقبس Docker لديك هي ما توقّف عن العمل، ولا أحد هناك ليصلحه. هذا يستحق وقفة تفكير بمعزل عن Docker 29 أصلًا. فإن كانت أداة تملك مقبسك، فحالة صيانتها جزء من وضعك الأمني، وقد كانت هذه الترقية تدقيقًا واضحًا بدرجة غير معتادة لمعرفة أي أدواتك ما زال لها مشرفون.[watchtower][portainer][jetbrains]

الترقية والتثبيت على إصدار والعودة

لا شيء في الآليات هنا يحتاج ذكاءً. الترقية معاملة apt أو dnf عادية بالحزم الخمس المعتادة، والطريقة الموثّقة لتثبيت إصدار محدّد هي نفسها الطريقة الموثّقة للعودة إلى إصدار. المهم هو الترتيب، ومعرفة ما الذي لن يتراجع عنه التراجع، مسبقًا.[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.

أمر أخير ينبغي معرفته إن كانت أتمتتك تحلّل مخرجات Docker النصية بدل استدعاء الـ API: غيّر الإصدار 29.0.0 حالة أحرف الحقول في docker version --format=json، ما كسر مجموعة Docker في Ansible بـ KeyError مجرّد. وقد صُحّح ذلك في 29.0.1، فلا يمسّ إلا من بقي على أول إصدار على الإطلاق — لكنه تذكير جيد بأن --format json واجهة لها إصدار، وأن التثبيت على إصدار تصحيحي تأمين رخيص لأي شيء يقرأ تلك المخرجات.[ansible]

التحقق، بالشكل الصحيح

"الخادوم أقلع" ليس تحققًا. نفّذ هذا قبل الترقية، واحتفظ بالمخرجات، ثم نفّذه مجددًا بعدها وقارن بين الاثنين — فالنقطة أن معظم ما تغيّر في الإصدار 29 هو قيمة افتراضية، والقيمة الافتراضية المتغيّرة تنتج مخرجات مختلفة لا خطأً.

#!/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 -u

أمران لا يستطيع السكربت فحصهما نيابةً عنك. الأول Compose: لا توجد مصفوفة توافق منشورة بين إصدارات Compose وإصدارات Engine API، والمسألة التي طُرحت في المنبع لطلب واحدة أُغلقت بوصفها سؤالًا، فالنهج الموثوق الوحيد هو تثبيت docker-compose-plugin الحالي من المستودع نفسه الذي يأتي منه المحرّك، بدل التنظير حول أي بناء قديم قد يظل صالحًا. والثاني كل عملية تمسك بمقبس Docker لديك — وكيل، أو مصدّر مقاييس، أو واجهة إدارة، أو runner للـ CI. كل واحدة منها تحمل عميل API خاصًا بها، وكل واحدة مرشّحة للقسم الأول من هذا المقال.[compose]

إذن، هل تُرقّي؟

الجواب نعم، والسبب ليس قائمة المزايا. يصنّف المصدر الرسمي docker-28.x على أنه غير مصان، ويعرّف غير المصان بأنه لم يعد يُطوَّر بنشاط، ولا يقبل المساهمات، وخارج نطاق النشرات الأمنية. والفرع المصان الآخر الوحيد هو 25.0، المُبقى على قيد الحياة لتوزيعتين لاحقتين بعينهما، ونهاية صيانته المتوقّعة في ديسمبر 2026 — أي بعد أربعة أشهر من الآن، ما يجعله شبكة أمان لا خطة. البقاء على 28 ليس الخيار المحافظ الذي يبدو عليه؛ إنه تشغيل إصدار غير مدعوم تفاديًا لإزعاج إصدار مدعوم.[branches]

حالتكالجواب الصادق
منظومة Compose على مضيف واحد، وصور تبنيها بنفسكرقِّ. دقّق في ulimit -n داخل الحاويات أولًا؛ فهو التغيير الوحيد المرجّح أن يفاجئك.
منفّذات CI تستخدم Docker-in-Dockerاختبر على runner واحد أولًا. ثبّت صورة خدمة DinD على الإصدار الرئيسي للخادوم، وغيّرهما معًا.
تعتمد على واجهة إدارة مستضافة ذاتيًاافحص حالة صيانتها قبل أن تفحص حالة Docker. في عدد من هذه الأدوات كانت الواجهة، لا المحرّك، هي العائق.
تصون قواعد جدار ناري مخصّصة في DOCKER-USERرقِّ — فهي تظل تعمل تحت واجهة iptables الافتراضية. ولا تعتمد nftables.
تعتمد على شبكات جسرية منفصلة كحدّ فاصلاقرأ قسم الشبكات قبل الترقية لا بعدها. هذا هو التغيير ذو العواقب الأمنية الحقيقية.
تبقى على 28 توخّيًا للسلامةdocker-28.x غير مصان في المنبع وخارج نطاق النشرات الأمنية. هذا هو الخيار الأخطر لا الأسلم.
  1. جرد العملاء قبل الخادوم. كل ما يمسك بمقبس Docker لديك يحمل عميل API خاصًا به. تلك القائمة — لا إصدار المحرّك — هي ما يحدّد إن كانت هذه الترقية حدثًا لا يُذكر أم عصرًا كاملًا من العمل.
  2. نفّذ تدقيق ulimit أولًا، وأنت على 28. شغّل docker run --rm ubuntu:24.04 bash -c 'ulimit -n' اليوم ودوّن أي أحمال العمل تتأثر. هذه أرخص عشر دقائق في الصفحة كلها، وهو التغيير الأكثر ترجيحًا للظهور كتراجع أداء غامض بعد أسبوعين.
  3. رقِّ في المكان. لا تُعد التثبيت. الترقية في المكان تُبقي overlay2 وتُبقي صورك ظاهرة. وإن كنت تعيد بناء المضيفات من أدوات إدارة الإعدادات، فقرّر عن قصد أي مخزن صور ينبغي أن يستخدمه المضيف المعاد بناؤه، لأن الافتراضي تغيّر تحت ذلك المسار.
  4. أعد فحص منافذك المنشورة. سلاسل العزل اختفت. وأي شيء تنشره على 0.0.0.0 صار قابلًا للوصول من حاويات في شبكات أخرى في حالات لم يكن كذلك فيها من قبل. انقل ما قصدت إبقاءه محليًا إلى 127.0.0.1.
  5. دع nftables وشأنه. إنه تجريبي، ويحذف سلسلة DOCKER-USER، وغير متاح في Swarm. لا يوجد سبب إنتاجي لاعتماده بعد.

إن كنت تُرقّي نظام التشغيل تحته أيضًا، فإن ترقية Ubuntu 24.04 إلى 26.04 على الخوادم يغطي الإصدار الذي يزيل cgroup v1 نهائيًا ويبدّل ستة إعدادات افتراضية أخرى في الطريق. وإن كانت قراءة هذا قد جعلتك تتساءل أصلًا عمّا إذا كانت منصة الحاويات تستحق تعقيدها، فإن متى لا تستخدم Kubernetes يعرض الحجة للجواب الأصغر، ومعماريات سحابية مملّة هو الحجة الأطول لاختيار بنية تحتية تتغيّر ببطء عن قصد.

الأسئلة الشائعة

ما الحد الأدنى لإصدار API في Docker Engine 29؟

يعتمد على الإصدار التصحيحي، وهي التفصيلة التي تغفلها معظم الأدلة. رفع الإصدار 29.0.0 الحدَّ الأدنى من 1.24 إلى 1.44، ثم خفّضه 29.3.0 مجددًا إلى 1.40. القيمة الافتراضية الحالية في الشفرة المصدرية هي 1.40، ولا يمكن بلوغ 1.24 إلا عبر تجاوز صريح. افحص خادومك أنت بـ docker version — يطبع قسم الخادوم إصدار الـ API والحد الأدنى في السطر نفسه — بدل الوثوق برقم من مقال كُتب أواخر 2025.

كيف أصلح خطأ "client version is too old. Minimum supported API version is 1.44"؟

حدّث العميل، وهو دائمًا تقريبًا أداة لا واجهة docker السطرية: Traefik 3.6.1، أو Portainer 2.33.5 LTS أو 2.36.0 STS، أو Testcontainers for Java 2.0.2، أو Docker SDK for Python 7.1.0، أو CapRover 1.14.1. وإن كنت عاجزًا فعلًا عن التحديث هذا الأسبوع، يمكن إخبار الخادوم بقبول إصدارات أقدم بضبط "min-api-version": "1.24" في /etc/docker/daemon.json (ولا يوجد خيار مكافئ في سطر الأوامر) أو DOCKER_MIN_API_VERSION=1.24 في ملف systemd إضافي. ويصف المصدر الرسمي كليهما بأنهما للحالات الاستثنائية، ويقول إن الإصدارات الأقدم ستُحذف في إصدار مقبل، دون نشر تاريخ.

لماذا اختفت كل صور Docker لديّ بعد الترقية إلى الإصدار 29؟

هي شبه مؤكد لم تختفِ. يجعل الإصدار 29 مخزنَ صور containerd الافتراضيَّ في التثبيت النظيف فقط، والواجهتان الخلفيتان لا ترى إحداهما محتوى الأخرى — ويصف المصدر الرسمي التبديل بأنه يُخفي الصور والحاويات بينما تبقى البيانات على القرص. فإن كنت قد أجريت ترقية حزم في مكانها فينبغي أن تكون ما تزال على overlay2 وأن ترى كل شيء؛ أما إن كنت قد أزلت الحزم وأعدت التثبيت، أو أعدت بناء المضيف من أدوات إدارة الإعدادات، فقد حصلت على الافتراضي الجديد. نفّذ docker info -f '{{ .DriverStatus }}' لمعرفة الواجهة الخلفية الفعّالة. ولاستعادة العرض القديم، اضبط "features": {"containerd-snapshotter": false} في daemon.json وأعد التشغيل. ولا تنفّذ docker system prune وأنت في حيرة من هذا الأمر.

لماذا بدأت حاوياتي تصطدم بخطأ "too many open files" بعد الترقية؟

لأن الحد الافتراضي لواصفات الملفات داخل الحاويات هبط من 1048576 إلى 1024. يحزم Docker Engine 29 نسخة containerd 2.1.5، التي توقّفت عن ضبط LimitNOFILE=infinity في وحدة systemd الخاصة بها، فصارت الحاويات ترث الافتراضي العادي لـ systemd. وقد أجرى Docker التغيير نفسه على حاويات البناء في v25.0، والإصدار 29 يوسّعه ليشمل كل الحاويات. أصلحه لكل حمل عمل عبر --ulimit nofile=65535:65535، أو اضبط default-ulimits في daemon.json. واختر رقمًا يمكنك تبريره بدل استعادة 1048576 — فالقيمة القديمة هي ما جرى التغيير للابتعاد عنها.

هل يكسر Docker 29 قواعد جداري الناري في DOCKER-USER؟

لا تحت واجهة iptables الافتراضية، حيث ما تزال DOCKER-USER موجودة وما تزال تعمل. ولا تختفي إلا إذا فعّلت عمدًا واجهة nftables التجريبية، حيث يذكر المصدر الرسمي صراحةً أنه لا توجد سلسلة DOCKER-USER وأن قواعدك لا تُرحَّل. وثمة فخّ في كيفية توقّفها عن السريان: تبديل مضيف قائم يترك القفزة القديمة من سلسلة FORWARD في مكانها، فتظل القواعد تُطلَق حتى تُزال تلك القفزة أو يُعاد إقلاع المضيف، بينما مضيف nftables مثبّت حديثًا يتجاهلها من البداية. أما ما تغيّر للجميع فهو حذف سلسلتَي DOCKER-ISOLATION-STAGE-1 وDOCKER-ISOLATION-STAGE-2، وهو ما يوسّع ما يمكن لحاويات في شبكة أن تصل إليه في شبكة أخرى. هذا تغيّر في نطاق الوصول لا في الجدار الناري، وهو الجزء من هذا الإصدار الجدير بالتدقيق.

هل أفعّل واجهة nftables في Docker 29؟

ليس على مضيف إنتاجي. إنها تجريبية صراحةً — يحذّر المصدر الرسمي من أن خيارات الإعداد والسلوك والتنفيذ قد تتغيّر جميعها — ولا يمكن تفعيلها والخادوم في وضع Swarm، وهي تحذف سلسلة DOCKER-USER. وعلى توزيعة حديثة فإن قواعد iptables عندك تُنفَّذ أصلًا عبر آلية nftables في النواة، فالمكسب العملي اليوم ضئيل. وإن أردت اختبارها فعلًا، فافعل ذلك على مضيف تحتمل أن تكون مخطئًا فيه بشأن الجدار الناري.

هل البقاء على Docker Engine 28 آمن؟

أقل أمانًا من الترقية، وهو عكس الشعور السائد. يصنّف جدول الفروع في المنبع docker-28.x على أنه غير مصان، ويعرّف ذلك بأنه لم يعد يُطوَّر بنشاط، ولا يقبل المساهمات، وخارج نطاق النشرات الأمنية. والفرعان المصانان الوحيدان هما docker-29.x و25.0، و25.0 مُبقى على قيد الحياة لتوزيعتين لاحقتين بعينهما، ونهاية صيانته المتوقّعة في ديسمبر 2026. التثبيت على 28 لأسبوعين ريثما تصلح عميلًا أمر معقول؛ أما تثبيته كسياسة فيعني تشغيل إصدار لن يتلقى إصلاحات أمنية.

هل يمكنني التراجع من Docker 29 إلى 28؟

نعم — ثبّت الإصدار الأقدم المحدّد عبر مدير الحزم، تمامًا كما يصف توثيق التثبيت. وثمة ثلاثة أشياء لا يتراجع عنها التراجع. إن كان الخادوم قد بُدّل إلى مخزن صور containerd، فلن يستطيع خادوم 28.x رؤية تلك الصور؛ فهي على القرص لكن ليست في مشغّل الرسم، ولا يوجد تحويل. والشبكة المُنشأة على 29 بطلب حجم بادئة من تجمّعات العناوين الافتراضية غير قابلة للاستخدام على خادوم أقدم ويجب حذفها وإعادة إنشائها. كما أن التثبيتات بلا صلاحيات الجذر ابتداءً من 29.5 لم تعد تتلقى slirp4netns عبر تحزيم Docker، فأعد تثبيته من توزيعتك إن عدت إلى بناء يتوقّعه.

هل ما يزال Docker Engine 29 يدعم cgroup v1؟

نعم. الإصدار 29 يُهمله، لكن المصدر الرسمي يلتزم بدعمه حتى مايو 2029، ويشير إلى أنه حتى حينها سيُبقي فرع مصان واحد على الأقل على الدعم. لا شيء على خوادمك يتوقّف هذا العام بسبب Docker. الضغط آتٍ من الاتجاه الآخر: حذف systemd التسلسلين القديم والهجين في المنبع، فتوزيعة Linux لديك ستُسقط cgroup v1 قبل Docker بوقت طويل. والمضيف الذي ما يزال يُقلع بـ systemd.unified_cgroup_hierarchy=0 ينبغي تنظيفه الآن، ما دام تغييرًا من سطر واحد لا عائقًا أمام ترقية.

المصادر

ملاحظات إصدار Docker وتوثيقها وشجرة شفرة moby هي المرجع لكل ما يخص المحرّك نفسه؛ ومتعقّبات المسائل الخاصة بالمشاريع المتأثرة هي المرجع لادعاءات التوافق. وثمة تحفّظان صادقان على القائمة. الإصدار الذي أُصلحت فيه كل أداة من أدوات الطرف الثالث مأخوذ عمومًا من ملاحظات إصدار ذلك المشروع لا من تقرير العلّة المرتبط هنا، فتابع المشروع نفسه إن احتجت إلى التأكد. وحيثما لا ينشر المصدر الرسمي تاريخًا أو إصدارًا — وحذف تجاوز إصدار الـ API هو الحالة البارزة — يقول المقال ذلك بدل التخمين.

  1. 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
  2. Docker — Docker Engine v29: Foundational Updates for the Future; the announcement post, and the source for the DOCKER_MIN_API_VERSION workaround
  3. 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
  4. Docker — Deprecated Engine features: the table that carries the cgroup v1 deprecation and its May 2029 support horizon
  5. 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
  6. Docker — Docker and nftables: the experimental backend, the tables it creates, and the statement that there is no DOCKER-USER chain
  7. Docker — Packet filtering and firewalls: the iptables model, the DOCKER-USER chain and the gateway modes referenced in the networking section
  8. Docker — dockerd reference: daemon.json keys, default-ulimits, storage-driver and the feature flags used in this article
  9. Docker — Configure the daemon, including the data directory location and why containerd's storage is configured separately
  10. Docker — Install Docker Engine on Ubuntu: the apt repository, the exact package set and the documented way to install a specific version
  11. Docker — Install Docker Engine on RHEL: the dnf repository and the equivalent version-pinning procedure
  12. Docker — Release lifecycle: the stages Docker applies to features and the notice it commits to before retiring them
  13. moby/moby — Branches and tags: the branch maintenance table showing docker-29.x maintained and docker-28.x unmaintained, and the definition of unmaintained
  14. moby/moby — daemon/config/config.go: the MaxAPIVersion, defaultMinAPIVersion and MinAPIVersion constants, and the comment describing min-api-version as an exceptional-case option
  15. moby/moby #51186 — daemon: raise minimum API version to v1.44, the change that shipped in 29.0.0
  16. 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
  17. moby/moby #49981 — the bridge iptables rework that removed the DOCKER-ISOLATION-STAGE-1 and DOCKER-ISOLATION-STAGE-2 chains
  18. moby/moby #50719 — legacy link environment variables are no longer added automatically, with the DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS escape hatch
  19. moby/moby #50476 — the --bridge-accept-fwmark daemon option that lets a firewall mark override Docker's drop rules
  20. moby/moby #50114 — requesting a prefix size from the default address pools, and the warning that such networks are unusable after a downgrade
  21. 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
  22. moby/moby #51111 — the cgroup v1 deprecation tracking issue for Docker Engine
  23. moby/moby #47377 — the userns-remap bug that keeps the containerd image store unavailable when user-namespace remapping is enabled
  24. containerd v2.1.5 — the runtime version packaged with Docker Engine 29, and the origin of the changed LimitNOFILE default
  25. Linux kernel documentation — Control Group v2, the hierarchy Docker will require once cgroup v1 support ends
  26. 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
  27. traefik #12253 — the Docker provider's hardcoded API version 1.24 against a v29 daemon, fixed by moving to version negotiation
  28. 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
  29. Portainer — Docker v29, and the fall-out: the most complete public inventory of management tools broken by the API floor and their fixed versions
  30. testcontainers-java #11235 — docker-java's default API version rejected by Engine 29, and the properties file workaround
  31. GitLab Runner #39129 — API version mismatches between the runner, the job image and the Docker-in-Docker service on Engine 29
  32. 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
  33. 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
  34. community.docker #1185 — KeyError: 'ApiVersion' from the docker version JSON shape change in 29.0.0, corrected in 29.0.1
  35. JetBrains IJPL-217878 — the IDE Docker plugin rejected by a v29 daemon until the plugin build shipped with the 2025.3 releases
  36. docker/compose #13371 — the absence of a published Compose-to-Engine API compatibility matrix, and why old Compose builds fail against v29

Was this useful?