الخطر الحقيقي: أسرارك تنتقل داخل صورك
في عام 2024، رصد GitGuardian أكثر من 12,8 مليون سر مكشوف في مستودعات GitHub العامة — بزيادة 28% مقارنة بالعام السابق وفق تقرير State of Secrets Sprawl 2025. وتُعدّ صور Docker Hub من أكثر المتجهات التي يُقلَّل من شأنها.
عندما تكتب ARG API_KEY في Dockerfile أو تمرر -e DB_PASSWORD=hunter2 عند بدء الحاوية، لا يبقى هذا السر محصوراً في وقت التشغيل. بل قد ينتهي به المطاف:
- في طبقات الصورة (قابل للفحص بـdocker history --no-trunc)؛
- في بيانات وصف الصورة المُصدَّرة على Docker Hub (docker inspect)؛
- في ملف .env المُضاف عن طريق الخطأ خلال git add . المتسرّع.
تجوب الماسحات الآلية (Trivy، Grype، GitGuardian) Docker Hub باستمرار. مستودع عام يحوي سراً بنص صريح يُفهرَس في غضون دقائق. نافذة الكشف تكاد تكون معدومة.
5 أخطاء في الضبط تكشف أسرارك
- متغيرات البيئة بنص صريح في
docker-compose.yml — environment: DB_PASSWORD: hunter2 يقرأه كل من يصل إلى الملف أو الصورة. - ملف
.env مُودَع في المستودع — خطأ واحد في .gitignore يُدخل السر في تاريخ git للأبد (حتى بعد git rm، لا يزال متاحاً عبر git log). -
ARG مُمرَّر عند البناء ثم مُدمَج في الصورة — تُخزَّن قيم ARG في بيانات طبقة الصورة ويمكن قراءتها بـdocker history. - الأسرار في السجلات — تطبيق يسجّل متغيرات بيئته عند الإقلاع (Spring Boot وRails في وضع debug وبعض خوادم Node) يطبع بيانات الاعتماد في
docker logs. - وحدات تخزين bind-mount على
/root أو دليل المشروع — ملف .env مُثبَّت من المضيف يظل في متناول أي عملية داخل الحاوية بصلاحيات root.
المتطلبات الأساسية
لمتابعة هذه المقالة، تحتاج إلى:
- خادم VPS Linux (Debian 12 أو Ubuntu 22.04+) بصلاحيات root.
- Docker Engine ≥ 24 وDocker Compose ≥ 2.24 (تحقق بـdocker compose version).
- لـSOPS: تثبيت age (حزمة age على Debian/Ubuntu، أو ثنائي من github.com/FiloSottile/age).
- لـ.env.vault: Node.js ≥ 18 وواجهة سطر الأوامر dotenvx (npm install -g @dotenvx/dotenvx).
لا يُشترط وجود مجموعة Swarm لأيٍّ من الطرق المقدَّمة هنا.
الطريقة الأولى — Docker Secrets في Compose بدون Swarm (خطوة بخطوة)
التحقق من إصدار Compose
يعمل Docker Secrets بدون Swarm منذ Docker Compose v2.24.0 (إصدار 11 يناير 2024). تحقق:
docker compose version # Docker Compose version v2.27.1إن كنت أدنى من v2.24، حدِّث Compose قبل المتابعة (
apt-get install docker-compose-plugin على Debian/Ubuntu).إنشاء ملفات الأسرار
أسرار Docker Compose في وضع غير Swarm هي ملفات على المضيف، مُثبَّتة كـtmpfs داخل الحاوية. أنشئها خارج دليل المشروع:
mkdir -p /etc/myapp/secrets echo -n 'strong-db-password' > /etc/myapp/secrets/db_password echo -n 'stripe-api-key-xxxxx' > /etc/myapp/secrets/stripe_key chmod 600 /etc/myapp/secrets/* chown root:root /etc/myapp/secrets/*الخيار
-n في echo يمنع إضافة سطر جديد في النهاية — بعض التطبيقات تقرأ الملف بأكمله بما في ذلك السطر الجديد، مما يُبطل المفتاح.الإعلان عن الأسرار في docker-compose.yml
services: app: image: myapp:latest secrets: - db_password - stripe_key environment: # نُشير إلى المسار، لا إلى القيمة DB_PASSWORD_FILE: /run/secrets/db_password STRIPE_KEY_FILE: /run/secrets/stripe_key secrets: db_password: file: /etc/myapp/secrets/db_password stripe_key: file: /etc/myapp/secrets/stripe_keyلاحظ استخدام اصطلاح
*_FILE: يجب أن يقرأ تطبيقك المتغير DB_PASSWORD_FILE، ويفتح الملف المُشار إليه ويقرأ محتواه. الصور الرسمية لـPostgreSQL وMySQL وRedis ومعظم صور Bitnami تدعم هذا الاصطلاح بشكل أصلي — راجع توثيق صورتك.التحقق من التثبيت داخل الحاوية
بعد
docker compose up -d، افحص التثبيت:docker compose exec app ls -la /run/secrets/ # -r-------- 1 root root 20 Sep 20 08:12 db_password # -r-------- 1 root root 28 Sep 20 08:12 stripe_key docker inspect myapp_app_1 | grep -A5 Mounts # "Type": "tmpfs", # "Destination": "/run/secrets",nوع التثبيت هو tmpfs: المحتوى يعيش في الذاكرة العشوائية، ولا يُكتب على قرص الحاوية. يختفي عند إيقاف الحاوية.
ما لا يفعله Docker Secrets — افهم ذلك قبل المتابعة
Docker Secrets ليس خزنة محكمة الإغلاق. ما لا يحمي منه:
- الملف المصدر (
/etc/myapp/secrets/db_password) يبقى على قرص المضيف بنص صريح — root على الخادم لديه دائماً وصول إليه.
- أي عملية داخل الحاوية (PID 1 أو عملية فرعية أطلقها التطبيق) يمكنها قراءة /run/secrets/*.
- متغير بيئة مشتق من السر (DB_PASSWORD=$(cat /run/secrets/db_password) في نقطة دخول) يُعيد السر إلى البيئة، مرئياً عبر docker inspect.يحمي Docker Secrets من التسرب في طبقات الصورة وفي
docker-compose.yml. لا يحمي من عملية مخترقة داخل الحاوية.
Docker Secrets مقابل .env.vault مقابل SOPS — أي طريقة لأي سياق
مرّر الجدول أفقيًا
| المعيار | Docker Secrets (Compose) | .env.vault (dotenvx) | SOPS + age |
|---|---|---|---|
| Swarm مطلوب | لا (منذ Compose v2.24) | لا | لا |
| السر مُخزَّن بنص صريح على المضيف | نعم (الملف المصدر) | لا (مشفَّر في المستودع) | لا (مشفَّر في المستودع) |
| KMS عن بُعد مطلوب | لا | لا (مفتاح محلي متماثل ممكن) | لا (age يعمل بلا اتصال) |
| التدوير بلا إعادة نشر | لا (إعادة تشغيل ضرورية) | لا (إعادة بناء .env) | لا (إعادة بناء .env) |
| CI/CD: الحقن في خط الأنابيب | معقد (ملفات للتوفير) | بسيط (متغير `DOTENV_PRIVATE_KEY`) | متوسط (مفتاح age كسر CI) |
| منحنى التعلم | منخفض (أصلي في Compose) | منخفض (واجهة dotenvx) | متوسط (صياغة YAML + مفاتيح age/GPG) |
الضبط التكميلي: التدوير والتشفير أثناء الراحة
تدوير أسرار Docker Compose. لا يدعم Docker Compose بدون Swarm التدوير الساخن (خلافاً لـSwarm الذي يمكنه تحديث السر دون إيقاف الخدمة). لتغيير سر:
# 1. كتابة القيمة الجديدة
echo -n 'new-password' > /etc/myapp/secrets/db_password
# 2. إعادة تشغيل الخدمة المعنية
docker compose restart appتشفير الملفات المصدر بـage. إن أردت تشفير الملفات على المضيف (لمواجهة سرقة لقطة أو نسخة احتياطية مخترقة)، يتيح لك SOPS + age تخزين ملفات مشفَّرة وفكّ تشفيرها عند الإقلاع:
# توليد مفتاح age
age-keygen -o /root/.config/sops/age/keys.txt
# تشفير ملف السر
sops --encrypt --age $(age-keygen -y /root/.config/sops/age/keys.txt) \
/etc/myapp/secrets/db_password > /etc/myapp/secrets/db_password.enc
# في سكريبت الإقلاع، فكّ التشفير قبل docker compose up
sops --decrypt /etc/myapp/secrets/db_password.enc > /etc/myapp/secrets/db_passwordمراجعة الوصول. فعِّل سجلات Docker مع journald (--log-driver=journald في /etc/docker/daemon.json) للاحتفاظ بسجل من أطلق أي حاويات ومتى.
ما لا يحميه Docker Secrets
يُثبِّت Docker Secrets السر كـtmpfs في /run/secrets/: إنه شبكة أمان ضد التسريب في الصور وملفات Compose، لا ضد عملية مخترقة داخل الحاوية. أي عملية تعمل داخل الحاوية — بما فيها shell حُصل عليه عبر RCE — يمكنها قراءة /run/secrets/*. وroot على المضيف يصل دائماً إلى الملف المصدر.
إن كان نموذج التهديد لديك يشمل حاوية مخترقة، فالجواب الصحيح هو مدير أسرار خارجي (HashiCorp Vault أو AWS Secrets Manager أو Infisical المستضاف ذاتياً) يُسلِّم الأسرار عبر API مع مصادقة، دون كتابتها على قرص الحاوية أبداً.
استكشاف الأخطاء وإصلاحها — أخطاء شائعة
unknown shorthand flag: 's' in -s أثناء docker compose up
أنت تستخدم الأمر القديم docker-compose (v1، Python). انتقل إلى docker compose (v2، إضافة Go) بـapt-get install docker-compose-plugin.
secrets are only supported when deploying to a swarm
إصدار Docker Compose لديك أقل من v2.24. تحقق بـdocker compose version وحدِّث.
الحاوية تبدأ لكن /run/secrets/db_password فارغ
تحقق من وجود الملف المصدر وعدم فراغه: cat /etc/myapp/secrets/db_password | wc -c. ملف فارغ يُنشئ تثبيت tmpfs فارغاً دون رسالة خطأ.
permission denied عند قراءة /run/secrets/
تُثبَّت الملفات بأذونات الملف المصدر. إن كانت عمليتك تعمل بمستخدم غير root داخل الحاوية، اضبط أذونات الملف على المضيف: chmod 640 /etc/myapp/secrets/db_password وتحقق من GID العملية.
docker inspect لا يزال يُظهر متغير البيئة بنص صريح
لقد أعلنت السر لكنك مررت القيمة أيضاً في environment:. احذف الإدخال بالقيمة المباشرة واستخدم اصطلاح *_FILE فقط في environment:.
أسرارك تحت السيطرة — والخطوات التالية
يُزيل Docker Secrets في وضع Compose غير Swarm السبب الرئيسي للتسريب: بيانات الاعتماد بنص صريح في ملفات الإعداد وطبقات الصورة. تنحصر الطريقة في خمس خطوات، وتعمل بلا بنية تحتية خارجية، وتندمج في أي سير عمل قائم.
للمزيد:
- قائمة التحقق للإنتاج: 10 نقاط لا غنى عنها لملف docker-compose.yml جاهز للإنتاج.
- البداية مع Docker على VPS: الشروع في استخدام Docker على VPS إن كنت تبني بيئتك الأولى.
- تصليب نظام التشغيل: قائمة تحقق تصليب Linux لتأمين طبقة المضيف التي يعمل عليها Docker.