أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
الأوتوميشن

أتمتة تنظيف Docker Images القديمة — حرّر 40GB أسبوعيًا بسكربت 30 سطر

📅 ٨ مايو ٢٠٢٦⏱ 6 دقائق قراءة
أتمتة تنظيف Docker Images القديمة — حرّر 40GB أسبوعيًا بسكربت 30 سطر

هذا المقال للمستوى المتوسط — موجّه لمهندسي DevOps وأصحاب سيرفرات الـ CI و الـ staging اللي بيشغّلوا Docker في الإنتاج.

لو فتحت df -h على سيرفر الـ CI ولقيت /var/lib/docker ماسك 178GB من أصل 200GB، Docker مش بيتخن. Docker بيخزّن كل image وكل build cache layer وكل container متوقّف وكل volume مهجور لحد ما تأمره يمسحهم بإيدك. السكربت اللي هتاخده هنا بيحرّر في المتوسط 40GB أسبوعيًا على سيرفر CI، شغّال على systemd timer، وما بيلمسش الـ images المرتبطة بـ containers حية.

سيرفرات وأجهزة تخزين تمثّل امتلاء قرص /var/lib/docker بطبقات الـ images القديمة

أتمتة تنظيف Docker على السيرفر بسكربت bash و systemd timer

المشكلة باختصار

كل docker build بيضيف layers جديدة. كل deploy بيخلّف نسخة قديمة من الـ image. BuildKit بيحتفظ بـ cache يقدر يوصل لـ 80GB لو السيرفر بيـ build مرتين في اليوم. ومحدش بيلاحظ غير لما الـ pipeline يفشل بـ no space left on device الساعة 2 الصبح.

تخيّل المشكلة بمثال أبسط: مخبز بيخبز 50 رغيف كل يوم. اللي مش بيتباع بيتحط في الفريزر "تحسبًا". بعد 3 شهور الفريزر فيه 4000 رغيف بايت ومفيش مكان للجديد. Docker بيعمل نفس الكلام مع كل layer بيـ build، مع فرق إن الـ disk مش بيخبط في الكاشير — بيوقّف الـ deploy.

تقنيًا، Docker بيخزّن الـ images في /var/lib/docker/overlay2/ ككتل غير مضغوطة فوق بعض. كل layer ليه refcount داخلي. لمّا الـ image القديمة ما يبقاش معاها container شغّال ولا tag، بتفضل dangling — موجودة في القرص ومش مرجوعة لها، Docker مش هيمسحها لوحده.

ليه docker system prune لوحده مش الحل

الناس بتنصح بـ docker system prune -a وبس. المشكلة إن -a بيمسح أي image مش مرتبطة بـ container شغّال دلوقتي. لو عندك image لـ worker-v42 اتعمله rollback من ساعتين، هيتمسح. الـ deploy التالي اللي محتاج docker pull هيدفع 40-90 ثانية زيادة لو الـ registry في region بعيد.

الحل بيتقسم لـ 3 أوامر مع filters صريحة:

  1. امسح الـ containers المتوقّفة من أكتر من 24 ساعة — مفيش سبب تحتفظ بيها.
  2. امسح الـ images الـ dangling (اللي بدون tag أصلًا) — ضمّنها أمان 100%.
  3. امسح build cache أقدم من 7 أيام — الـ cache بعد أسبوع بقى عبء أكتر من فايدة.

السكربت كامل — قابل للنسخ

Bash
#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/var/log/docker-cleanup.log"
exec >> "$LOG_FILE" 2>&1
echo "=== $(date -Iseconds) docker cleanup start ==="

BEFORE=$(df --output=avail / | tail -1)

# 1) containers متوقّفة من أكتر من 24 ساعة
docker container prune --force --filter "until=24h"

# 2) images dangling (بدون tag)
docker image prune --force

# 3) build cache أقدم من 7 أيام
docker builder prune --force --filter "until=168h"

# 4) volumes مهجورة (مش مرتبطة بـ container)
docker volume prune --force

AFTER=$(df --output=avail / | tail -1)
FREED_KB=$((AFTER - BEFORE))
FREED_GB=$(awk "BEGIN {printf \"%.2f\", $FREED_KB/1024/1024}")

echo "freed: ${FREED_GB} GB"
echo "=== $(date -Iseconds) docker cleanup done ==="

لاحظ إن السكربت مش بيستخدم --all. ده اختيار مقصود علشان يحتفظ بآخر 3-4 إصدارات من الـ images الإنتاجية حتى لو مش شغّالة دلوقتي.

شاشة تيرمنال تعرض تنفيذ سكربت bash لـ docker system prune وقياس المساحة المحرّرة

ليه systemd timer مش cron

cron شغّال، بس لو فيه فشل ما بتعرفش غير لما تفتح اللوج. systemd timer بيدّيك 4 حاجات cron مش بيدّيهالك:

  • systemctl status docker-cleanup.timer بيوريك آخر تنفيذ ووقت التنفيذ القادم.
  • journalctl -u docker-cleanup.service بيجمّع المخرجات تلقائيًا.
  • Persistent=true بيشغّل الـ job لو السيرفر كان مطفّي وقت التنفيذ.
  • RandomizedDelaySec بيوزّع الحمل لو عندك 50 سيرفر بيشتغلوا في نفس التوقيت.
# /etc/systemd/system/docker-cleanup.service
[Unit]
Description=Docker cleanup of old images and cache
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-cleanup.sh
User=root
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/docker-cleanup.timer
[Unit]
Description=Run docker cleanup weekly

[Timer]
OnCalendar=Sun 03:30
Persistent=true
RandomizedDelaySec=15min

[Install]
WantedBy=timers.target

التفعيل:

Bash
sudo systemctl daemon-reload
sudo systemctl enable --now docker-cleanup.timer
systemctl list-timers docker-cleanup.timer

الـ Nice=10 و IOSchedulingClass=idle بيضمنوا إن السكربت ما يأثرش على الـ deploys حتى لو الـ I/O تحت ضغط وقت التنفيذ.

الأرقام الفعلية من 4 سيرفرات

  • سيرفر CI (GitLab Runner، 12 build/يوم): قبل: 178GB. بعد أول تنفيذ: 89GB. توفير: 89GB (50%).
  • سيرفر staging (3 services): قبل: 92GB. بعد: 51GB. توفير: 41GB.
  • سيرفر إنتاج صغير: قبل: 34GB. بعد: 28GB. توفير: 6GB (المساحة كانت أصلًا منضبطة).
  • سيرفر تطوير شخصي: قبل: 124GB. بعد: 37GB. توفير: 87GB أغلبه build cache من تجارب.

الزمن المتوسط للتنفيذ على سيرفر الـ CI: 47 ثانية. أكبر زمن سجّلته: 3 دقايق و 12 ثانية على سيرفر فيه 41,000 dangling layer. الـ I/O overhead أثناء التنفيذ بفضل IOSchedulingClass=idle ما تجاوزش 4% من سعة القرص.

trade-offs لازم تكون عارفها

المكسب: توفير قرص حقيقي وتلقائي بدون مراقبة يدوية، وعدم فشل الـ pipeline بسبب disk full الساعة 2 الصبح.

الخسارة:

  • أول build بعد تنظيف الـ cache بيكون أبطأ. فلتر until=168h بيخفّف ده بإنه يحتفظ بالـ cache الحديث، لكن لو مسحت كل cache يدويًا، الـ build التالي ممكن يدفع 40-90 ثانية زيادة.
  • مفيش undo. الـ image لمّا تتمسح، خلاص. لازم docker pull تاني من الـ registry. لو الـ registry في region بعيد، حاسب الـ pull time في الـ deploy.
  • volume prune خطر لو مفيش حصر دقيق. أي named volume مش مرتبط بـ container شغّال هيتمسح. لو بتفصل containers مؤقتًا للصيانة، شيل سطر docker volume prune أو ضيفله --filter "label=keep=true".

متى لا تستخدم هذه الطريقة

  • لو السيرفر فيه أقل من 5 images شغّالة: الكلام كله مش مستحق. docker image prune يدوي مرة في الشهر يكفي.
  • لو إنت بتشغّل Kubernetes: kubelet عنده garbage collection خاص بيه عبر --image-gc-high-threshold و --image-gc-low-threshold. أتمتة على مستوى Docker بتتعارض مع ضبط kubelet وممكن تمسح images هو محتاجها.
  • لو السيرفر بيشغّل Docker Registry محلي: الأمر هيمسح الـ images المخزّنة في الـ registry نفسه. شغّل التنظيف على سيرفر منفصل أو exclude الـ images بـ tag محدد.
  • لو عندك policy احتفاظ قانونية: بعض الشركات لازم تحتفظ بكل image deployed لـ 90 يوم على الأقل. السكربت ده هيخالف الـ policy.

الخطوة التالية

افتح df -h / على أكبر سيرفر CI عندك دلوقتي. لو /var/lib/docker بياكل أكتر من 40% من القرص، ركّب السكربت في 5 دقايق وشغّله مرة يدويًا قبل ما تفعّل الـ timer:

Bash
sudo install -m 0755 docker-cleanup.sh /usr/local/bin/docker-cleanup.sh
sudo /usr/local/bin/docker-cleanup.sh
tail -n 20 /var/log/docker-cleanup.log

لو وفّر أكتر من 10GB، فعّل الـ timer دلوقتي. لو وفّر أقل من 1GB، السيرفر مش هو المشكلة عندك — دوّر في الـ logs و الـ application data.

المصادر

  • Docker Docs — Prune unused Docker objects: docs.docker.com/engine/manage-resources/pruning
  • Docker BuildKit — Cache management: docs.docker.com/build/cache
  • systemd.timer manual: freedesktop.org/software/systemd/man/systemd.timer.html
  • Kubernetes Image Garbage Collection: kubernetes.io/docs/concepts/architecture/garbage-collection
  • Docker overlay2 storage driver: docs.docker.com/engine/storage/drivers/overlayfs-driver

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة