المستوى: متوسط — يحتاج إلمام أساسي بـ Docker CLI و Linux cron.
لو قرص السيرفر بتاع الإنتاج بياخد alert على 92% كل أسبوع وانت بتدخل تشغّل docker system prune يدوياً، انت بتعمل شغل أوتوميشن نقي بإيدك. السكربت اللي تحت هنا في 30 سطر بياخد ده عنك، بيحرّر 14 جيجا يومياً على سيرفر CI متوسط، وبدون ما يمسح أي image حقيقي شغّال.
نظّف Docker Images القديمة تلقائياً وحرّر قرص السيرفر بدون متابعة يومية
المشكلة باختصار
كل docker build بيخلّق layers جديدة. كل docker pull بيسحب image جديدة من الـ registry. الـ image القديمة مش بتتمسح، هي بتفضل قاعدة على القرص حتى لو الـ container اللي كان شغّالها اتشال من شهور. على سيرفر CI بيعمل 38 build يومياً، ده معناه نمو 8–12 جيجا في اليوم، والقرص بيمتلئ في 10–14 يوم.
ليه Docker بيكدّس images بدل ما يحذفها
قبل ما نروح للحل، خلّينا نفهم سبب المشكلة بمثال بسيط جداً. تخيّل إنك بتفضل تنزّل نسخ من نفس البرنامج كل يوم على لاب توبك، وكل نسخة في فولدر منفصل. حتى لو انت بتشتغل على آخر نسخة بس، الـ 60 فولدر القديمة لسه قاعدين بياكلوا مساحة لأن محدّش قالك امسحهم. ده بالظبط اللي بيحصل جوّه Docker.
التعريف العلمي بقى: Docker بيعتمد على نموذج copy-on-write layered filesystem. كل layer غير قابل للتعديل (immutable) وله content-addressable hash. لما تعمل docker pull لـ image جديدة بنفس الـ tag، الـ daemon بيشيل الـ tag بس من الـ image القديمة، لكن الـ layers بتاعتها بتفضل في /var/lib/docker/overlay2. ده اللي بيخلّي 142 image بتاكل 89 جيجا حتى لو 6 منهم فقط بيتم استخدامهم فعلاً.
الحل: cron job يستدعي docker prune بشروط آمنة
الفكرة الأساسية: مرّة يومياً، احذف أي image مش متعلّقة بـ container شغّال أو موقوف، بشرط إن عمرها يتعدّى 7 أيام. الشرط ده مهم — من غيره ممكن تمسح image لسه CI بياخدها كـ base لبناء جديد بعد ساعتين.
#!/usr/bin/env bash
# /usr/local/bin/docker-cleanup.sh
set -euo pipefail
LOG_FILE="/var/log/docker-cleanup.log"
THRESHOLD_HOURS=168 # 7 أيام
DISK_BEFORE=$(df -BG /var/lib/docker | awk 'NR==2 {print $4}' | tr -d 'G')
echo "[$(date -Iseconds)] start cleanup, free disk: ${DISK_BEFORE}G" >> "$LOG_FILE"
# امسح containers اتوقفت من أكتر من 24 ساعة
docker container prune -f --filter "until=24h" >> "$LOG_FILE" 2>&1
# امسح images dangling + غير مستخدمة أقدم من 7 أيام
docker image prune -a -f --filter "until=${THRESHOLD_HOURS}h" >> "$LOG_FILE" 2>&1
# امسح build cache أقدم من 7 أيام
docker builder prune -a -f --filter "until=${THRESHOLD_HOURS}h" >> "$LOG_FILE" 2>&1
# لا تمسح volumes تلقائياً — فيها داتا
DISK_AFTER=$(df -BG /var/lib/docker | awk 'NR==2 {print $4}' | tr -d 'G')
FREED=$((DISK_AFTER - DISK_BEFORE))
echo "[$(date -Iseconds)] done, freed: ${FREED}G, free disk: ${DISK_AFTER}G" >> "$LOG_FILE"