SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

أوامر Docker prune لتوفير مساحة القرص على VPS

هل امتلأ قرص VPS بسبب Docker؟ تعرّف أولاً على استهلاك الصور والحاويات وذاكرة البناء ووحدات التخزين، ثم احذف غير الضروري بأمان دون فقد بيانات.

اعرف ما الذي يستهلك مساحة القرص قبل حذف أي بيانات غير مستخدمة

يستهلك Docker مساحة القرص على VPS في أربعة مواضع: الصور، والحاويات المتوقفة، وذاكرة التخزين المؤقت للبناء، ووحدات التخزين المحلية. شغّل docker system df أولاً لمعرفة العنصر الذي يستهلك المساحة، ثم شغّل أمر الحذف غير الضروري الأضيق نطاقاً الذي يزيله. الترتيب مهم، لأن الأمر الأخير في هذا الدليل، docker volume prune -a، يحذف البيانات ولا يمكن التراجع عنه.

ابدأ بنظام الملفات، وليس بـDocker.

df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -h

يُظهر لك df مدى امتلاء القرص. ويُظهر du أين استُهلكت المساحة. يحصر الخيار -x عملية du في نظام ملفات واحد، لذلك لا يتابع عملية mount إلى وحدة تخزين منفصلة ولا يحسبها مرتين. توجد خمسة أدلة مهمة هنا: يحتوي overlay2 على طبقات الصور والحاويات، ويحتوي volumes على بيانات وحدات التخزين، ويحتوي containers على بيانات تعريف الحاويات وملفات السجلات، ويحتوي buildkit على ذاكرة التخزين المؤقت للبناء، ويحتوي image على بيانات تعريف الطبقات.

ملاحظة حول sudo وwildcards الخاصة بالصدفة، لأنهما يسببان إضاعة الكثير من الوقت. يملك root المسار /var/lib/docker، ولا يستطيع المستخدم العادي قراءته، لذلك يعرض ls /var/lib/docker النتيجة Permission denied. ويفشل أيضاً أمر مثل sudo du -sh /var/lib/docker/*، لأن الصدفة توسّع * قبل تشغيل sudo، ولا تستطيع الصدفة قراءة ذلك الدليل. تستخدم كل الأوامر أدناه find أو --max-depth بدلاً من wildcard لهذا السبب تحديداً.

والآن، اعرض طريقة Docker نفسها في القياس.

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          24        6         12.4GB    8.91GB (71%)
Containers      31        6         1.42GB    1.39GB (97%)
Local Volumes   14        5         6.03GB    4.11GB (68%)
Build Cache     212       0         9.87GB    9.87GB

تأتي هذه الأرقام من جهاز واحد ولا تخبرك بشيء عن جهازك. انظر إلى النمط بدلاً من الرقم. يحسب TOTAL عدد الكائنات، ويحسب ACTIVE الكائنات المستخدمة حالياً، أما RECLAIMABLE فهو تقدير Docker للمساحة التي يمكن لحذف البيانات غير المستخدمة تحريرها من ذلك الصف.

هناك نقطتان في RECLAIMABLE تسببان الالتباس. يحسب طبقات الصور المشتركة مرة واحدة لكل صورة تستخدمها، لذلك يقدّر صف الصور عادةً مساحة أكبر مما يمكنك تحريره فعلياً. كما أنه لا يتضمن ملفات سجلات الحاويات مطلقاً، لأن Docker لا يتعامل مع ملف السجل بوصفه كائناً قابلاً للاسترداد. عندما يعرض du دليلاً أكبر بكثير من المساحة التي يقر بها docker system df، تكون ملفات السجلات هي السبب، وهناك قسم يشرح ذلك أدناه.

أضف -v لعرض التفاصيل حسب كل كائن.

docker system df -v

يقسم ذلك الملخص إلى قسم مستقل لكل نوع من الكائنات. يضيف قسم الصور عمودي SHARED SIZE وUNIQUE SIZE، بحيث يمكنك معرفة التكلفة الفعلية لصورة واحدة. ويضيف قسم وحدات التخزين عدّاد LINKS، وهو عدد الحاويات المرتبطة بوحدة التخزين تلك. تذكّر LINKS، لأن القيمة 0 هي الاختبار الكامل الذي تعتمد عليه أوامر حذف وحدات التخزين غير المستخدمة.

الصور المعلّقة مقابل الصور غير المستخدمة

تبدو هاتان العبارتان قابلتين للتبادل، لكنهما ليستا كذلك. تعمل عوامل التصفية بشكل مختلف لأن الكائنات مختلفة.

الصورة المعلّقة هي صورة بلا وسم. تظهر بالشكل <none> في docker images. تُنشئ واحدة عند كل إعادة بناء: ينقل docker build -t myapp:latest . وسم myapp:latest إلى الصورة الجديدة، بينما تحتفظ الصورة القديمة بكل طبقاتها، لكنها تفقد اسمها. لا يشير إليها أي شيء، ولا ينظفها أي مكوّن تلقائياً.

الصورة غير المستخدمة هي أي صورة، موسومة أو غير موسومة، لا يشير إليها أي container حالياً. إنّ postgres:16 التي سحبتها الشهر الماضي ولا تشغّلها الآن غير مستخدمة، لكنها ليست معلّقة.

docker image prune        # dangling images only
docker image prune -a     # every image no container refers to

يسألك الخيار الثاني أولاً.

WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]

اقرأ هذا السؤال بعناية. تعني عبارة «Associated to them» وجود كائن container، سواء كان قيد التشغيل أو متوقفاً. إذا نفّذت docker compose down، فستُحذف الـcontainers، ولذلك تصبح كل صورة استخدمتها تلك الخدمات غير مستخدمة، ويؤدي -a إلى حذفها جميعاً. لا يُفقد شيء لا يمكنك استعادته، لكن عملية docker compose up -d التالية ستسحب المجموعة كاملة أو تعيد بناءها، وهذا يستهلك عرض النطاق الترددي ووقت البناء على VPS صغير. لذلك من المهم عملياً معرفة ما الذي يزيله docker compose down وما الذي يتركه stop قيد التشغيل قبل تنفيذ أي عملية تنظيف.

تُبقي التصفية الصور الحديثة خارج النطاق.

docker image prune -a --filter "until=240h"

يزيل ذلك الصور غير المستخدمة التي أُنشئت قبل أكثر من 240 ساعة (10 أيام)، ويُبقي الصور الأحدث. تقبل قيمة until سلسلة مدة بصيغة Go مثل 240h، أو طابعاً زمنياً مطلقاً مثل 2026-08-01T00:00:00.

ما هي ذاكرة التخزين المؤقت للبناء ولماذا تنمو بلا حدود

يُعد BuildKit أداة البناء التي يستخدمها Docker افتراضياً مع docker build وdocker compose build منذ Docker Engine 23.0. يخزّن نتائج كل خطوة من كل Dockerfile يشغّله، ويحتفظ بذاكرة التخزين المؤقت هذه في /var/lib/docker/buildkit. تفسّر ذاكرة التخزين المؤقت سبب اكتمال البناء الثاني خلال ثوانٍ، وهذا يعني أنها تؤدي وظيفتها. لكن المشكلة هي أن الإدخالات القديمة لا تنتهي صلاحيتها افتراضياً. إذا بنيت الصورة نفسها خمسين مرة باستخدام خطوة COPY تتغير في كل مرة، فستحتفظ بخمسين مجموعة من الطبقات.

لا تظهر ذاكرة تخزين البناء المؤقتة ضمن docker image prune. فهي نوع مستقل من الكائنات وله أمره الخاص.

docker builder prune                        # dangling cache
docker builder prune -a                     # all unused cache
docker builder prune --filter until=168h    # cache untouched for 7 days

لا تؤثر أي من هذه الأوامر في صورك أو بياناتك. التكلفة الوحيدة لمسح ذاكرة تخزين البناء المؤقتة هي أن عملية البناء التالية ستستغرق وقتاً أطول مرة واحدة. في VPS يعيد بناء الصور بانتظام، تكون Build Cache غالباً أكبر خانة في docker system df، ما يجعلها أكبر عنصر آمن يمكنك حذفه.

أوامر التنظيف مرتبة من الآمن إلى المدمّر

نفّذ الأوامر بالترتيب وتوقّف فوراً عندما يبدو df -h / سليماً مجدداً. يطبع كل أمر سطر Total reclaimed space: عند اكتماله.

  1. يزيل docker container prune الحاويات المتوقفة. تُزال معها طبقاتها القابلة للكتابة، لذلك تُحذف أيضاً أي بيانات كتبتها الحاوية خارج volume. لا تتأثر volumes.
  2. يزيل docker image prune الصور غير المرتبطة فقط. هذا أكثر أوامر الصور أماناً.
  3. يزيل docker builder prune ذاكرة التخزين المؤقت للبناء غير المرتبطة. التكلفة هي تنفيذ عملية بناء بطيئة مرة واحدة.
  4. يزيل docker image prune -a كل صورة لا تشير إليها أي حاوية. التكلفة هي إعادة السحب أو إعادة البناء.
  5. ينفّذ docker system prune الأوامر الثلاثة الأولى دفعة واحدة، ويضيف إليها الشبكات غير المستخدمة.
  6. يزيل docker volume prune الـanonymous volumes غير المستخدمة.
  7. يزيل docker volume prune -a الـvolumes غير المستخدمة، بما في ذلك الـnamed volumes. هذا هو الأمر الذي يحذف قواعد البيانات.

يحدّد docker system prune نطاقه بنفسه قبل التنفيذ.

WARNING! This will remove:
        - all stopped containers
        - all networks not used by at least one container
        - all dangling images
        - unused build cache
Are you sure you want to continue? [y/N]

حُذفت الـvolumes من القائمة عمداً. تؤدي إضافة --volumes إلى إعادة الـanonymous volumes إلى النطاق. وتوسّع إضافة -a خطوة الصور من الصور غير المرتبطة إلى كل صورة غير مستخدمة. إن تنفيذ docker system prune -a --volumes -f كاملاً على خادم إنتاج هو الطريقة التي يفقد بها المستخدمون البيانات أثناء محاولة تحرير مساحة.

لماذا يؤدي تنظيف volumes إلى حذف قاعدة بياناتك

هذا هو القسم الذي يجب قراءته مرتين.

يُعد volume غير مستخدم عندما لا يكون أي container مرتبطاً به. هذا هو الاختبار الكامل. لا يتحقق Docker مما إذا كان الـvolume فارغاً، أو مما إذا كان ملف compose لا يزال يعرّفه، أو مما إذا كان يحتوي على النسخة الوحيدة من قاعدة بياناتك. يعني LINKS 0 في docker system df -v أنّه قابل للتنظيف، ولا يعني شيئاً آخر.

نفّذ الآن إجراءين عاديين بالتتابع. تستخدم docker compose down لإعادة تشغيل stack بطريقة نظيفة. يزيل ذلك الـcontainers ويُبقي الـvolumes المسماة في مكانها، وهذا بالضبط ما توضحه الوثائق. يصبح volume الخاص بـPostgres غير مرتبط بأي شيء. بعد عشر دقائق، تستخدم docker volume prune -a لتحرير المساحة، فتُحذف قاعدة البيانات. نفّذ الأمران عملهما بصورة صحيحة. لكن تسلسل الأوامر أتلف البيانات.

منذ Docker Engine 23.0 ‏(API version 1.42)، أصبح الأمر العادي أضيق نطاقاً مما كان عليه.

WARNING! This will remove anonymous local volumes not used by at least one container.

الـanonymous volume هو volume أنشأه Docker لك، عادةً لأن image تعرّف VOLUME ولم تمنحه اسماً. تحتوي هذه الـvolumes عادةً على بيانات لم تطلب الاحتفاظ بها. لا يُحذف الـnamed volume، وهو النوع الذي كتبته في ملف compose، إلا عند إضافة -a. كانت إصدارات Docker الأقدم تحذف النوعين باستخدام الأمر العادي، لذلك لا تعتمد على عادات اكتسبتها في خادم قمت بترقيته منذ ذلك الحين. لا يتضح هذا الفرق إلا بعد معرفة اختلاف الـnamed volumes عن bind mounts، لأن الـbind mount ليس Docker volume أصلاً، ولن يلمسه أي أمر prune.

تحقق قبل الحذف. استبدل myapp_pgdata باسم الـvolume الذي تتحقق منه.

docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_data

يعني المرشح dangling=true على volume أنّه غير مُشار إليه، وليس أنّه فارغ. يعرض _data المحتوى الموجود فعلياً بداخله. إذا وجدت دليلاً باسم pgdata أو mysql، فتوقف وأنشئ نسخة قبل المتابعة. يحدث الإتلاف نفسه من خلال docker compose down -v، إذ يزيل كل volume يعرّفه ملف compose ولا يطلب منك تأكيداً مسبقاً.

الـvolume هو الشيء الوحيد على مضيف Docker الذي لا يستطيع إعادة البناء إنشاءه من جديد. لذلك يجب حفظ بيانات الـvolumes في نسخة restic احتياطية تعمل خارج الخادم، حيث لا يستطيع خيار مكتوب خطأً الوصول إليها.

عندما لا يؤدي أي إجراء إلى تنظيف المساحة: ملفات سجلات الحاويات

لقد نظّفت كل شيء، ويعرض docker system df مساحة قابلة للاسترداد تكاد تكون معدومة، لكن القرص لا يزال ممتلئاً. افحص السجلات.

sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10

تكتب كل حاوية مخرجاتها القياسية وأخطاءها القياسية في ملف JSON ضمن /var/lib/docker/containers/. في التثبيت الافتراضي، تكون max-size غير معيّنة، ما يعني عدم وجود حد أقصى. لذلك قد تواصل حاوية واحدة عالقة في حلقة تعطل كتابة السجل حتى يمتلئ القسم. لا يزيل أي أمر تنظيف هذه الملفات، لأن الحاويات التي تنتجها قيد التشغيل، ولذلك لا يمكن تنظيفها بهذا الأمر بحكم التعريف.

لا تحذف الملف. إن تشغيل rm على ملف سجل مفتوح لا يحرر أي مساحة، لأن Docker daemon لا يزال يحتفظ بواصف ملف مفتوح، ويبقي kernel الكتل مخصصة حتى يُغلق ذلك المقبض. ولن يتحرك df على الإطلاق. استخدم truncate بدلاً من ذلك؛ فهو يحافظ على inode نفسه، ويسمح للـdaemon بمتابعة الكتابة.

sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /

هذا حل مؤقت. لا يعيد docker logs الآن أي نتيجة لتلك الحاويات، وتبدأ الملفات بالنمو مجدداً فوراً. الحل الفعلي هو تدوير السجلات، وهو موضح في القسم التالي.

القياس قبل التنفيذ وبعده، في كل مرة

لا تخمّن نتيجة عملية prune. خذ قراءة، ونفّذ أمراً واحداً، ثم خذ قراءة أخرى.

df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /

قارن بين مخرجات df. هذا هو الرقم الوحيد الذي يحدد ما إذا كان خادمك سيواصل تقديم الخدمة. ثم يوضح لك docker system df أي صف تغيّر فعلياً، وتطبع كل عملية prune قيمة Total reclaimed space: الخاصة بها.

إذا لم تتغير df، لكن أظهر docker system df أنه تم تحرير مساحة، فهذا يعني أن واصف ملف مفتوحاً يحتفظ بالكتل المحذوفة. هذه هي مشكلة ملف السجل المذكورة أعلاه. إذا تغيرت القيمتان ثم امتلأ القرص مجدداً خلال يوم، فالمشكلة هي نمو البيانات وليست مشكلة تنظيف. ويكون الحل باستخدام التدوير مع مهمة مجدولة.

كيفية منع امتلاء القرص مرة أخرى

ضع حداً لحجم السجلات. أنشئ الملف /etc/docker/daemon.json أو حرّره.

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

يحد ذلك سجلات كل حاوية إلى 30 MB. يجب أن تكون كل قيمة ضمن log-opts سلسلة نصية، بما في ذلك القيم الرقمية. تحقّق من إمكانية تحليل الملف قبل إعادة التشغيل، لأن وجود daemon.json غير صالح يمنع daemon من بدء التشغيل، ويؤدي إلى توقف جميع الحاويات معه.

python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'

يجب أن يعرض docker info الآن القيمة Logging Driver: json-file. تظهر الحدود نفسها ضمن قسم LogConfig في docker inspect للحاوية التي أُنشئت بعد إعادة التشغيل. وهذه هي النقطة المهمة: ينطبق هذا الإعداد على الحاويات الجديدة فقط. تحتفظ الحاويات الحالية بالإعدادات التي أُنشئت بها، لذلك أعد إنشاءها.

docker compose up -d --force-recreate

يمكن أيضاً تحديد الحد لكل خدمة في ملف compose. وهذا أفضل عندما تحتاج خدمة كثيرة السجلات إلى قيمة مستقلة.

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

جدولة عملية تنظيف محدودة. شغّلها أسبوعياً، مع حصرها في الصور غير المرتبطة وذاكرة التخزين المؤقت القديمة لعمليات البناء. لا تضع -a أو --volumes في مهمة مجدولة أبداً، لأن المهمة التي تعمل أثناء توقف stack ستحذف صور ذلك stack، ومع --volumes ستبدأ العمل على بياناتك.

sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-prune

ينفّذ السطر الأخير البرنامج النصي مرة يدوياً، لكي ترى مخرجاته قبل تشغيله تلقائياً. يجب أن يكون الملف قابلاً للتنفيذ، ويجب ألا يحتوي اسمه على نقطة، لأن run-parts يتجاهل كل ما ليس قابلاً للتنفيذ وكل ما يحمل امتداداً.

أطلق تنبيهاً عند انخفاض المساحة الحرة. عملية التنظيف التي تشغّلها بعد امتلاء القرص هي إجراء استرداد. أما التنبيه عند وصول الاستخدام إلى 80 بالمئة فهو إجراء وقائي.

usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"

ضع ذلك في cron مع أداة الإشعارات التي تستخدمها حالياً. المساحة الحرة ليست سوى نصف الصورة، لذلك اربط التنبيه بـمراقبة حالة القرص على VPS، لأن القرص المتعطل والقرص الممتلئ يوقفان حاوياتك معاً، لكن كل حالة منهما تحتاج إلى إصلاح مختلف.

يفترض كل ما سبق تثبيتاً قياسياً يكون فيه جذر البيانات ضمن /var/lib/docker. إذا نقلته باستخدام المفتاح data-root في daemon.json، فاستبدل المسار بمسارك في كل أمر. ويُعد ضبط هذا التخطيط بشكل صحيح على خادم جديد جزءاً من إعداد Docker على VPS، كما أن اتخاذ القرار قبل أن تتراكم 40 GB من الحاويات على القسم الخطأ أسهل بكثير.

FAQ

هل يحذف docker system prune وحدات التخزين الخاصة بي؟

لا. يزيل الأمر الأساسي الحاويات المتوقفة، والشبكات غير المستخدمة، والصور المعلّقة، وذاكرة التخزين المؤقت غير المستخدمة لعمليات البناء. وتعرض مطالبة التأكيد هذه المجموعة بالضبط. لا تشمل العملية وحدات التخزين إلا عند إضافة --volumes. ومنذ Docker Engine 23.0، يشمل هذا الخيار وحدات التخزين المجهولة بدلاً من وحدات التخزين المسماة. تُحذف وحدات التخزين المسماة باستخدام docker volume prune -a وdocker compose down -v. يجب توخي الحذر مع هذين الأمرين.

لماذا ما زال القرص ممتلئاً بعد تشغيل docker prune؟

هناك سببان شائعان. الأول هو ملفات سجلات الحاويات ضمن /var/lib/docker/containers/. لا يلمسها أي أمر prune، وتستمر في النمو بلا حد حتى تضبط max-size. والثاني هو ملف محذوف ما زالت إحدى العمليات تفتحه. إذا حذفت سجلاً باستخدام rm بينما كانت حاويته قيد التشغيل، يحتفظ daemon بمُعرّف الملف، ولا تحرر النواة الكتل. لذلك لا يعرض df أي تغيير. قارن sudo du -xh --max-depth=1 /var/lib/docker مع docker system df لمعرفة الحالة الموجودة لديك.

ما الفرق بين docker image prune وdocker image prune -a؟

يزيل الأمر الأساسي الصور المعلّقة فقط. وهي الصور التي فقدت وسمها، ويحدث ذلك غالباً بسبب إعادة البناء. يزيل الشكل -a كل صورة لا تشير إليها أي حاوية موجودة، بما في ذلك الصور الموسومة التي نزّلتها عمداً. بعد تنفيذ docker compose down، تختفي الحاويات، ولذلك سيحذف -a صور تلك المجموعة أيضاً. لا يُفقد شيء نهائياً، لأن التشغيل التالي ينزّل الصور أو يعيد بناءها. لكن ذلك قد يستغرق وقتاً طويلاً على اتصال بطيء.

كيف أمنع سجلات Docker من ملء القرص؟

اضبط max-size وmax-file ضمن log-opts في /etc/docker/daemon.json، ثم أعد تشغيل daemon باستخدام sudo systemctl restart docker. ينطبق الإعداد على الحاويات التي تُنشأ بعد إعادة التشغيل فقط. لذلك أعد إنشاء الحاويات قيد التشغيل باستخدام docker compose up -d --force-recreate. يمكنك ضبط الخيارين نفسيهما لكل خدمة في ملف compose ضمن مفتاح logging. استخدم ذلك عندما تكون إحدى الخدمات أكثر إصداراً للسجلات بكثير من بقية الخدمات.

هل من الآمن تشغيل docker system prune من مهمة cron؟

يُعد docker system prune -f الأساسي آمناً على مضيف تبقى فيه كل المجموعات قيد التشغيل. لكنه يزيل الحاويات المتوقفة، ولذلك سيحذف حاوية أوقفتها عمداً وكنت تنوي إعادة تشغيلها لاحقاً. المهمة المجدولة الأكثر أماناً هي docker image prune -f مع docker builder prune -f --filter until=168h. فهي تحرر العنصرين اللذين ينموان بأسرع معدل، ولا يمكنها الوصول إلى أي وحدة تخزين. لا تجدول -a أو --volumes مطلقاً.