الأمن السيبراني

Docker rootless وأمان الحاويات: درس 2026

8 دقائق للقراءة

وضع rootless في Docker (وفي Podman افتراضياً) يقلّص بشكل كبير سطح الهجوم: إن اخترق مهاجم حاوية، فلن يحصل على root على المضيف.

راجع دليل Docker الكامل.

لماذا تشغّل Docker في وضع rootless

افتراضياً، يشتغل daemon Docker كـ root. ثغرة في dockerd أو حاوية سيئة العزل قد تعطي وصول root إلى VPS — سيناريو كارثي لخادم يستضيف بيانات عملاء أو أعمال multi-tenant. وضع rootless يُطلق daemon تحت مستخدم غير ممتاز ويستعمل user namespaces في Linux لتخطيط UIDs. مهاجم يهرب من الحاوية يحطّ في فضاء مستخدمك، لا في root.

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

تفعيل Docker rootless

# تثبيت التبعيات
sudo apt install -y uidmap dbus-user-session

# كمستخدم عادي
dockerd-rootless-setuptool.sh install

# تفعيل عند الإقلاع
systemctl --user enable docker
loginctl enable-linger $(whoami)

# متغيّرات البيئة
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/1000/docker.sock' >> ~/.bashrc

قيود rootless

  • لا ربط منافذ < 1024 بلا setcap أو إعداد sysctl
  • لا cgroup v1 (يلزم v2 — الافتراضي الحديث)
  • Networking: slirp4netns، أبطأ قليلاً من bridge
  • Volumes: صلاحيات تحتاج عناية

أمان حاوية أساسي

# USER غير root في Dockerfile
USER 1000:1000

# إسقاط capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

# نظام ملفات للقراءة فقط
docker run --read-only --tmpfs /tmp myapp

# منع التصعيد
docker run --security-opt=no-new-privileges myapp

# حدود
docker run --memory=512m --cpus="0.5" myapp

الخطوة 1: متطلبات kernel والحزم

rootless يتطلب kernel Linux 5.13+ مع cgroup v2. Ubuntu 22.04 و24.04 متوافقتان. تحقّق قبل البدء لتجنّب تثبيت ناقص.

uname -r
# يجب أن يعرض 5.13+ مثالياً 6.x

stat -fc %T /sys/fs/cgroup
# يجب أن يعرض cgroup2fs

sudo apt install -y uidmap dbus-user-session fuse-overlayfs slirp4netns

النتيجة المنتظَرة: cgroup2fs مؤكَّد. إن ظهر tmpfs، فأنت على cgroup v1. فعّل v2 بـ systemd.unified_cgroup_hierarchy=1 في GRUB وأعد التشغيل.

الخطوة 2: تثبيت Docker في وضع rootless

السكربت الرسمي dockerd-rootless-setuptool.sh يهيّئ كل شيء تحت مستخدمك. لا تُطلقه أبداً بـ sudo: يجب أن يُنفَّذ بصفتك.

curl -fsSL https://get.docker.com/rootless | sh
# اتبع تعليمات السكربت

# أضف إلى .bashrc
export PATH=/home/USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
source ~/.bashrc

systemctl --user start docker
systemctl --user enable docker
loginctl enable-linger USER

يجب أن تحصل: docker version يعرض client وserver، بلا Cannot connect to the Docker daemon. الـ linger يضمن نجاة daemon من خروجك من SSH.

الخطوة 3: التحقق من أن كل شيء يدور بلا root

أطلق حاوية اختبارية وافحص العمليات. تأكيد أن dockerd والحاوية يشتغلان تحت UID الخاص بك.

docker run --rm hello-world
ps -u $USER | grep -E 'dockerd|hello'
id -u    # UID المضيف الخاص بك

يجب أن تحصل: لا عملية root مرتبطة بـ Docker. كامل السلسلة يشتغل تحت UID مستخدمك. إن رأيت dockerd بـ PID 1 تحت root، فقد شغّلت الخدمة الخطأ — عطّل خدمة النظام بـ sudo systemctl disable --now docker.

الخطوة 4: قيود يجب معرفتها

في rootless، لا يمكنك ربط منافذ أقل من 1024 (80، 443) بلا إعداد إضافي. الحلّ النظيف: reverse proxy على المضيف (Caddy أو Nginx) يستمع على 80/443 ويُعيد التوجيه إلى حاوياتك على 8080/8443. بديل: sudo setcap cap_net_bind_service=ep $(which rootlesskit) لكنه يفتح ثغرة أمنية.

الشبكة تستخدم slirp4netns افتراضياً، الذي يحدّ حوالي 1 Gbps. لأحمال شبكية مكثفة، انتقل إلى pasta بـ --driver pasta، أسرع ومتوافق مع وضع rootless منذ 2024.

الخطوة 5: إعداد reverse proxy Caddy على المضيف

Caddy المثبَّت كحزمة apt يشتغل بمستخدمه ويستمع شرعياً على 80/443. يُعيد التوجيه إلى حاويتك rootless.

sudo apt install -y caddy

# /etc/caddy/Caddyfile
api.exemple.com {
  reverse_proxy localhost:8080
}

sudo systemctl reload caddy

ما يجب أن تراه: Caddy يحصل تلقائياً على شهادة Let’s Encrypt ويُوكّل نحو حاويتك. تحقّق بـ curl -I https://api.exemple.com الذي يجب أن يُرجع 200 أو 404 (حسب تطبيقك)، لا 502.

الخطوة 6: استخدام volumes في rootless

volumes المسمّاة تُنشأ وتُستخدم كما في وضع root، لكنها مخزَّنة في ~/.local/share/docker. bind mounts المضيف تشتغل فقط على ملفات يستطيع مستخدمك قراءتها.

docker volume create app-data
docker run -d --name pg \
  -v app-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=ChangeMe2026 \
  postgres:17-alpine
docker volume ls

المخرَج المنتظَر: volume app-data دائم بين إعادات تشغيل الحاوية. للنسخ الاحتياطي: docker run --rm -v app-data:/data alpine tar czf - /data > backup.tar.gz.

الخطوة 7: حدود الموارد بلا capabilities root

الرايتان --memory و--cpus تشتغلان في rootless إن كان cgroup v2 نشطاً ومفوَّضاً لمستخدمك. تحقّق بـ cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers.

docker run -d --name api \
  --memory 512m --cpus 0.5 \
  -p 8080:3000 my-api:1.0
docker stats --no-stream

ما يجب أن تراه: عمود MEM USAGE / LIMIT يعرض جيداً ... / 512MiB. إن لم يظهر الحدّ (ترى ... / 7.7GiB)، فـ cgroup v2 غير مفوَّض. الحلّ: sudo systemctl edit user@.service وأضف Delegate=yes.

الخطوة 8: المراقبة والسجلات

السجلات متاحة عبر journalctl --user -u docker لـ daemon، وdocker logs <container> للأحمال. لرصد مركزي، اربط Grafana Loki موجّهاً برنامج تشغيل log json-file نحو وكيل Promtail.

journalctl --user -u docker --since "1 hour ago"
docker logs --tail 100 -f api

النتيجة النمطية: السجلات تتدفق آنياً. لـ VPS بلا stack رصد، --tail 100 -f يبقى الأسرع للإطلاق عند حادث.

مزالق شائعة

مزلق أول: محاولة استخدام --privileged في rootless. معظم الامتيازات لا يمكن منحها بلا root على المضيف — الراية تُتجاهَل جزئياً صامتة. إن احتجت privileged فعلاً، فتصميم حاويتك خاطئ على الأرجح. مزلق ثانٍ: iptables وNAT المخصّص لا يشتغلان في user namespace. الحلّ: استخدم شبكة bridge الافتراضية وانشر المنافذ. مزلق ثالث: ربط /var/run/docker.sock في حاوية (نمط Docker-in-Docker المضاد) لا يشتغل كما هو متوقَّع في rootless لأن socket في ~/.docker/run/.

حالات استخدام نمطية

الحالة 1 — VPS مشترَك بين مطورين: لكل مطور daemon rootless، عزل قوي بلا امتيازات متقاطعة. الحالة 2 — مختبر محلي للطلاب: لا مخاطرة بأن يكسر طالب الآلة كـ root. الحالة 3 — CI/CD self-hosted runner: runner GitHub Actions يشتغل rootless، عزل حسب builds. الحالة 4 — اختبارات أمن على حاويات: rootless يسمح باختبار صور مشبوهة بلا منح root لعملٍ مريب.

راجع أيضاً مقارنة Docker مقابل Podman.

الخطوة 9: تحصين إضافي بـ seccomp وAppArmor

وضع rootless يقلّص السطح، لكن Docker يطبّق افتراضياً ملف seccomp مقيِّداً يُصفّي استدعاءات النظام الخطرة. تحقّق أنه نشط ولم يُعطَّل سهواً في أوامر docker run.

docker info | grep -i seccomp
# يجب أن يعرض: seccomp Profile: builtin

# سرد capabilities الحاوية:
docker run --rm alpine sh -c 'cat /proc/self/status | grep Cap'

النتيجة المنتظَرة: مجموعة capabilities محدودة، بلا CAP_SYS_ADMIN ولا CAP_NET_ADMIN. أي راية --security-opt seccomp=unconfined يجب أن تكون مبرَّرة — تعطّل 60+ مرشّح وتضاعف خطر الهروب عند ثغرة kernel.

الخطوة 10: استخدام صور مصغّرة

صورة قائمة على Alpine تزن 5 ميغا، مقابل 80 ميغا لـ Ubuntu و800 ميغا لصورة Debian كاملة مع build-essential. حزم أقل تعني سطح هجوم أقل وتنزيلاً أسرع.

FROM alpine:3.20
RUN apk add --no-cache nodejs npm
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER 1000
CMD ["node", "server.js"]

المخرَج المرجعي: صورة نهائية 60-90 ميغا حسب تبعيات Node، مقابل 200-400 ميغا انطلاقاً من node:22. التوجيه USER 1000 يمنع تشغيل التطبيق كـ root داخل الحاوية، حماية مزدوجة مع rootless المضيف.

الخطوة 11: تدقيق الصور بـ Trivy

Trivy يكشف CVE لحزم صورتك قبل النشر. أدمجه في CI أو شغّله محلياً قبل كل docker push.

sudo apt install -y trivy
trivy image --severity HIGH,CRITICAL my-api:1.0
trivy config Dockerfile

المخرَج المنتظَر: قائمة CVE مصنّفة حسب الخطورة مع الرقم والإصدار المُصحَّح. الهدف: صفر CRITICAL مفتوح مع تصحيح متاح. HIGH يمكن تحمّلها إن خصّت حزمة غير معروضة للشبكة (مثل lib مساعدة داخلية).

الخطوة 12: نسخ احتياطي واستعادة rootless

volumes والبيانات في ~/.local/share/docker. نسخ بسيط بإيقاف daemon وأرشفة المجلد — أو الأفضل، تصدير كل volume منفصلاً لاستعادة انتقائية.

systemctl --user stop docker
tar czf docker-backup-$(date +%F).tar.gz \
  -C ~/.local/share docker
systemctl --user start docker

المخرَج المنتظَر: أرشيف يحتوي كل volumes وميتاداتا Docker. خزّنها على تخزين كائنات خارجي (R2، B2، MinIO) مع تشفير client-side عبر age للبيانات الحساسة.

قائمة فحص أمنية

إعداد rootless نظيف يُصادَق بخمس نقاط: daemon يشتغل تحت user، cgroup v2 مفوَّض لحدود الموارد، ملف seccomp builtin نشط، صور Alpine أو distroless مع USER غير root، فحص Trivy بلا CRITICAL مفتوح. مدمجاً مع reverse proxy Caddy في الواجهة وجدار UFW صارم على المضيف، هذا الإعداد يقاوم الغالبية العظمى من الهجمات الانتهازية المستهدفة لـ VPS المعروضة على الإنترنت.

أسئلة شائعة rootless

هل rootless بنفس أداء وضع root؟ شبه متطابق للأحمال CPU والذاكرة. الشبكة slirp4netns تحدّ حوالي 1 Gbps؛ لأحمال شبكية مكثفة، انتقل إلى pasta الذي يمحو هذا الحدّ. التشغيل البارد لحاوية أبطأ بـ 100 إلى 200 مللي ثانية من root، مهمل لخدمة long-running.

هل أستطيع تشغيل Kubernetes في rootless؟ نعم، k3s وkind يدعمان rootless منذ 2022. للإنتاج متعدد العقد، أبقِ عقداً مخصّصة على root، rootless مفيد خاصة على محطة تطوير وCI runners.

هل يشتغل Compose v2 مع rootless؟ نعم، شفاف تماماً. الأمر docker compose up يستخدم socket مستخدمك بلا إعداد إضافي. كل ملفات docker-compose.yml الموجودة تشتغل بلا تعديل.

كيف أُهاجر تثبيت Docker root موجوداً إلى rootless؟ صدّر volumes الحرجة كـ tar، أزل daemon النظام، ثبّت rootless، أعد إنشاء volumes واستورد البيانات. احسب ساعة لإعداد بـ 5-10 خدمات وبضع جيغابايتات.

تشغيل إنتاجي حقيقي

للنشر الإنتاجي على VPS Hetzner CX22 أو OVH VPS Starter، النمط الموصى به يجمع أربع طبقات: جدار UFW يأذن فقط لـ 22، 80 و443، Docker rootless للأحمال التطبيقية، Caddy على المضيف كـ reverse proxy مع HTTPS تلقائي، وfail2ban لحظر IP التي تُغرق SSH. هذه الـ stack تسع على 2 vCPU و4 جيغا RAM مع عشرة حاويات خفيفة، بتكلفة شهرية أقل من 8 USD حسب المزوّد. النسخ نحو Backblaze B2 أو Cloudflare R2 تضيف أقل من 1 USD/شهر للأحجام النمطية لموقع عمل.

للتعمّق

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité