SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-10-11

ملفات env و الأسرار في Docker Compose: الفرق والأولوية

تعرّف على الفرق بين `.env` و`env_file` و`environment` في Docker Compose، واعرف أيها ينتصر ولماذا لا ينبغي وضع كلمات المرور في متغيرات البيئة.

الأشياء الثلاثة التي يسمّيها الناس ملف env

يحتوي Docker Compose على 3 آليات منفصلة ذات أسماء متشابهة ومربكة. يملأ ملف .env العناصر النائبة ${VARIABLE} داخل compose.yaml نفسه، بعد أن يقرأ Compose ملف YAML وقبل أن يتحقق من صحته أو يشغّل أي شيء. وتحمل السمة env_file: ملفاً يحتوي على أزواج مفتاح/قيمة إلى بيئة الحاوية. أما السمة environment: فتعيّن المتغيرات مباشرةً على الحاوية، وتُكتب في ملف compose. هذه الآليات ليست قابلة للتبادل. وعندما تعيّن آليتان منها المفتاح نفسه، يُحسم الفائز وفق ترتيب أولوية موثّق.

يوضح هذا الدليل كيفية عمل كل آلية، ويثبت الأولوية باستخدام أمر يمكنك تشغيله، ثم يتناول النقطة الأهم: يمكن لأي شخص يستطيع تشغيل docker inspect قراءة متغيرات البيئة، لذلك لا تضع كلمات المرور فيها. إذا كنت جديداً على ملفات compose عموماً، فابدأ بـ أساسيات Docker Compose على VPS ثم عُد إلى هنا لمعرفة إعدادات التهيئة.

ملف .env مخصّص لملف Compose، وليس للحاوية

أنشئ دليلاً وضع فيه ملفين.

mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

اطلب الآن من Compose عرض ما حلّله فعلياً.

docker compose config

يُظهر الناتج image: alpine:3.20. اختفى العنصر النائب لأن الاستبدال حدث أثناء تحميل Compose للملف. يبحث Compose عن .env في دليل المشروع: الدليل المحدد باستخدام --project-directory إذا عيّنته، وإلا فدليل أول ملف مرّرته باستخدام -f، وإلا فالدليل الحالي. ويستبدل عناصر ${NAME} النائبة التي يجدها في قيم YAML. لا تُستبدل المفاتيح أبداً، وينتج عن $$ مضاعف علامة دولار حرفية بدلاً من إجراء الاستبدال.

شغّل الخدمة الآن.

docker compose run --rm demo

يخرج printenv ALPINE_TAG بحالة 1 ولا يطبع شيئاً. المتغير غير موجود داخل الحاوية. هذا هو سوء الفهم الأكثر شيوعاً: يضبط .env ملف Compose، وليس العملية. وجود ملف .env يتضمن POSTGRES_PASSWORD=hunter2 لا يفعل شيئاً لقاعدة بياناتك ما لم يُشر إليه جزء من ملف Compose.

يوفّر ${NAME:-default} قيمة احتياطية عندما يكون المتغير غير معيّن أو فارغاً. ويجعل ${NAME:?message} Compose يرفض البدء ويطبع رسالتك، وهذا هو الخيار الصحيح لقيمة لا تملك قيمة افتراضية آمنة.

env_file يحمّل المتغيرات إلى الحاوية

تحدّد السمة env_file: ملفاً واحداً أو عدة ملفات تصبح محتوياتها متغيرات بيئة داخل الحاوية.

printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

يعرض هذا الأمر from_env_file. يتكوّن تنسيق الملف من أسطر KEY=value عادية، سطر واحد لكل متغير، ويبدأ التعليق بالرمز #. هذا التنسيق ليس تنسيق shell، مع أن Compose يفسّره وفق بعض القواعد المشابهة لقواعد shell. تُحذف علامات الاقتباس المحيطة بالقيمة، لذلك يعطي KEY="value" وKEY=value النتيجة نفسها. تُوسَّع العناصر النائبة ${...} في القيم المحاطة بعلامات اقتباس مزدوجة والقيم غير المحاطة بعلامات اقتباس، بينما تُستخدم القيم المحاطة بعلامات اقتباس مفردة حرفياً. يمكن استخدام بادئة export، لكنها غير مطلوبة. تُحذف المسافات حول علامة =. انتبه إلى التعليق المضمّن: في القيمة غير المحاطة بعلامات اقتباس، تبدأ مسافة متبوعة بـ # تعليقاً، لذلك عليك إحاطة أي قيمة تحتوي على # بعلامات اقتباس.

يُعد مسار env_file المفقود خطأً، ويتوقف Compose. اجعل الملف اختيارياً إذا كان غيابه متوقعاً ومشروعاً:

    env_file:
      - path: ./app.env
        required: false

تضبط environment المتغيرات inline

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

يُسمح بصيغتين: صيغة mapping أعلاه، وصيغة list التي تستخدم - GREETING=from_environment. وتتصرف الصيغتان بالطريقة نفسها. وتشتركان أيضاً في حيلة إضافية: يمرّر المفتاح الذي لا قيمة له المتغير من shell الذي شغّلت فيه docker compose. في صيغة list، يكون ذلك - GREETING مجرداً، وفي صيغة mapping يكون GREETING: من دون أي شيء بعد النقطتين.

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

يطبع ذلك from_my_shell. شغّله من دون ضبط GREETING في shell، وسيضبط Compose لا شيء من دون إصدار تحذير. من المهم معرفة حالات فشل التمرير الصامتة، لأن الخدمة التي تبدأ مع متغير كلمة مرور فارغ غالباً ما تبدأ بنجاح، لكنها تكون مكشوفة بالكامل.

ما الذي تكون له الأولوية

يوثّق Docker ترتيب الأولوية، من الأعلى إلى الأدنى: docker compose run -e في سطر الأوامر، ثم environment أو env_file التي تُستبدل قيمتها من الصدفة أو من ملف بيئة، ثم environment العادية في ملف Compose، ثم env_file، ثم توجيه ENV المضمّن في الصورة.

الخلاصة للاستخدام اليومي: تتغلب environment: على env_file:، بينما تتغلب -e في سطر الأوامر على كليهما. أثبت ذلك في ملف واحد.

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
    environment:
      GREETING: from_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

يطبع الأمر الأول from_environment، ما يعني أن environment: تجاوزت القيمة الموجودة في app.env. ويطبع الأمر الثاني from_cli. لا يوجد شيء في ملف Compose يتجاوز قيمة سطر الأوامر.

عندما يتصرف أحد الحاويات كما لو أن إعداداتك لم تُطبَّق، فلا تعتمد على التخمين. يطبع docker compose config الملف المحلَّل بالكامل، بينما يطبع docker compose config --environment متغيرات الاستبدال التي يستخدمها Compose. يتضح في معظم بلاغات «ملف البيئة الخاص بي مُهمل» أن القيمة محددة مرتين على مستويين مختلفين.

لماذا تتسرّب متغيرات البيئة

عند تعيين كلمة مرور في environment:، تُخزَّن في إعدادات الحاوية على القرص، ويمكن لأي مستخدم ينتمي إلى مجموعة docker رؤيتها.

docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'

يحتوي الناتج على "DB_PASSWORD=hunter2" بنص واضح. وتكشف ثلاثة مسارات أخرى القيمة نفسها. يطبع docker compose config القيمة في الطرفية، وهكذا ينتهي بها الأمر ملصقةً في منتدى دعم. ترث العمليات الابنة المتغير افتراضياً، ما لم يبدأها الأب عمداً ببيئة بديلة. ويمكن لعملية تعمل بالمستخدم نفسه الذي يعمل به PID 1، أو تملك CAP_SYS_PTRACE، قراءته من /proc/1/environ. وفي حاوية تعمل فيها جميع العمليات بصفة root، ينطبق ذلك على كل عملية. أما التطبيق الذي يعمل كمستخدم غير مميّز بينما يعمل PID 1 بصفة root، فسيحصل بدلاً من ذلك على خطأ صلاحيات عند الوصول إلى ذلك الملف، مع بقاء المتغير في بيئته الخاصة. كما أن معالجات تعطل التطبيقات تفرغ البيئة كاملةً بشكل متكرر في سجل أو تقرير خطأ.

تُعدّ العضوية في مجموعة docker عملياً امتياز root على المضيف، لذلك لا يمكنك الاعتماد عليها كحدّ للصلاحيات. يشرح الدليل حسابات المستخدمين ذات أقل قدر من الصلاحيات على VPS سبب أهمية تقييد هذه المجموعة على أي خادم مشترك.

تحافظ أسرار Compose على القيمة في ملف

يدعم Compose الأسرار المستندة إلى الملفات. تُركَّب القيمة داخل الحاوية كملف بدلاً من حقنها في البيئة.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

يُركَّب السر في المسار /run/secrets/db_password داخل الحاوية. والاسم الذي يلي الشرطة المائلة هو اسم السر من كتلة secrets: ذات المستوى الأعلى.

اللاحقة _FILE هي اصطلاح تستخدمه Docker Official Images، بما في ذلك postgres وmysql وmariadb. تبحث نصوص entrypoint هذه عن VARNAME_FILE، وتقرأ الملف، ثم تستخدم محتواه. هذه ليست ميزة في Docker، ولذلك لا تعمل إلا عندما تطبّقها الصورة. راجع وثائق الصورة قبل افتراض أن SOMETHING_FILE سيُحترم. يمكن للتطبيقات التي لا تدعمها قراءة الملف بنفسها عند بدء التشغيل، أو يمكنك تمرير المسار وترك entrypoint الخاص بك يتولى ذلك.

تحقق من ذلك من داخل الحاوية قيد التشغيل:

docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD

يعرض الأمر الأول كلمة المرور. ولا يعرض الأمر الثاني شيئاً، لأن القيمة لم تدخل البيئة قط. هذه هي الفكرة كاملة: يعرض docker inspect في هذه الحاوية المسار غير الحساس فقط.

احمِ الملف المصدر على المضيف، لأن السر لا يتمتع إلا بقدر الخصوصية الذي يتمتع به الملف الذي يخزنه:

chmod 600 db_password.txt

الحل العملي المتوازن على VPS

لا تدعم كثير من الصور المستضافة ذاتياً متغيرات _FILE، لذلك تكون متغيرات البيئة وسيلة الدخول الوحيدة. على VPS يديره مسؤول واحد، الهدف الواقعي هو منع بقاء القيم في ملف يمكن للجميع قراءته داخل دليل المشروع، وإبقاؤها خارج git.

sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env
    env_file:
      - /etc/myapp/app.env

ينشئ install -m 600 الملف مع ضبط نمط الصلاحيات مسبقاً، لذلك لا توجد فترة يكون فيها قابلاً للقراءة من الجميع. يملكه root، لذا لا يستطيع مستخدم عادي على الخادم قراءته، لكن أي شخص يستطيع تشغيل docker يمكنه مع ذلك قراءة القيمة من الحاوية. أضف *.env و.env إلى .gitignore، ثم أودِع ملف app.env.example يحتوي على أسماء المفاتيح بقيم فارغة بدلاً من ذلك. كلمة المرور التي أودِعت في المستودع هي كلمة مرور يجب تغييرها.

يعني تغيير قيمة إعادة تشغيل الخدمة. تُقرأ متغيرات البيئة مرة واحدة عند بدء عملية الحاوية، لذلك لا يغيّر تعديل الملف شيئاً حتى تشغّل docker compose up -d --force-recreate db. هذا هو النمط نفسه المستخدم في دليل n8n خلف HTTPS على VPS، حيث يوجد مفتاح التشفير خارج ملف compose.

تقسيم الإعدادات حسب البيئة

يقرأ Compose الملف .env من دليل المشروع تلقائياً. وجّهَه إلى موقع آخر باستخدام --env-file.

docker compose --env-file .env.staging config

تُقرأ الملفات المتعددة بالترتيب، وتستبدل الملفات اللاحقة الإعدادات الموجودة في الملفات السابقة. احتفظ بالقيم الافتراضية غير السرية في ملف مضمّن في المستودع، وبالأسرار في ملف لا يغادر الخادم أبداً. وينطبق الأمر نفسه على env_file:، إذ تكون قيمة المفتاح المكرر في آخر ملف مُدرج هي المعتمدة.

FAQ

لماذا يتجاهل ملف .env داخل الحاوية؟

لا يتم تجاهله. يستبدل ملف .env العناصر النائبة ${NAME} في ملف compose، ويمكنه أيضاً ضبط متغيرات Compose الخاصة مثل COMPOSE_PROJECT_NAME أو COMPOSE_FILE. لكنه لا يضبط أي متغيرات داخل الحاوية. لإدخال القيمة إلى الحاوية، أشر إليها باستخدام environment: { KEY: "${NAME}" }، أو استخدم env_file: ./that-file.env بدلاً من ذلك.

هل يتغلب environment على env_file، أم يحدث العكس؟

تكون الأولوية لـenvironment:. يضع الترتيب الموثق في Docker السمة environment قبل السمة env_file، وتأتي كلتاهما بعد docker compose run -e في سطر الأوامر. إذا ضُبط مفتاح في الموضعين، تُستخدم القيمة الموجودة في env_file بصمت.

كيف أرى القيمة النهائية التي سيستخدمها Compose؟

شغّل docker compose config لطباعة ملف compose المحلَّل بالكامل بعد تطبيق جميع عمليات الاستبدال. بالنسبة إلى حاوية قيد التشغيل، يعرض docker inspect <container> --format '{{json .Config.Env}}' القيمة التي استلمتها العملية بالضبط.

هل تكون أسرار Compose مشفَّرة؟

لا. يُثبَّت السر المعتمد على ملف داخل الحاوية كملف نصي عادي في /run/secrets/<name>، ويبقى الملف المصدر على قرص المضيف من دون تشفير. الفائدة هنا هي تحديد النطاق، لا التشفير: تبقى القيمة خارج بيئة الحاوية، وخارج ناتج docker inspect، وخارج تفريغات الأعطال التي تطبع البيئة.

هل يمكنني استخدام علامات الاقتباس والمسافات في ملف env؟

نعم. يزيل Compose علامات الاقتباس المحيطة، لذلك تعطي KEY="value with spaces" وKEY=value with spaces كلتاهما القيمة value with spaces. استخدم علامات الاقتباس المفردة عندما تحتوي القيمة على $ يجب ألا يخضع للاستبدال، وضع علامات اقتباس حول أي قيمة تحتوي على #، لأن هذا الرمز يبدأ تعليقاً إذا لم يكن مقتبساً. تُزال المسافات المحيطة بـ=، لذلك يظل KEY = value يضبط KEY.

هل كان هذا المقال مفيدًا؟