EduFlow

الوحدة 3 · بيئات محلية مع Docker Compose

ملفات env والـ Overrides ووضع Watch

20 دقراءة 4 داملأ الفراغات

الدرس في لمحة

انقل إعدادات notes-api إلى ملفات env وأسرار Compose، وافصل إعدادات التطوير وحدها في ملف override وفي profiles، واحصل على حلقة تعديل سريعة بـ docker compose watch.

  1. 1نوعان من البيئة
  2. 2أسرار Compose
  3. 3ملف أساسي مع override
  4. 4وضع Watch: حلقة تعديل سريعة

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

ملف Compose الخاص بـ notes-api يعمل الآن، وفيه ثلاث مشاكل تصطدم بها الفرق في الأسبوع الثاني. كلمة مرور قاعدة البيانات مكتوبة نصًّا صريحًا في ملف محفوظ في المستودع. وإعدادات التطوير وحدها، مثل منافذ التشخيص، مختلطة بما يحتاج إليه الـ CI. وكل تغيير في الكود يعني docker compose up --build وانتظار ثلاثين ثانية. هذا الدرس يصلح الثلاث.

نوعان من البيئة

لدى Compose آليتان منفصلتان تتعلّق كلتاهما بـ «env»، والخلط بينهما سبب معظم الالتباس في الإعدادات.

الاستبدال (interpolation) يملأ العناصر النائبة ${VAR} في ملف Compose نفسه. القيم تأتي من الـ shell لديك، ثم من ملف .env في مجلد المشروع:

.env
POSTGRES_PASSWORD=change-me-locally
API_PORT=8080
compose.yaml
services:
  api:
    ports:
      - "${API_PORT:-8080}:3000"
  db:
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}

الصيغة ${API_PORT:-8080} تستخدم 8080 قيمةً احتياطية إن لم يكن المتغيّر مضبوطًا. والصيغة ${POSTGRES_PASSWORD:?…} تجعل القيمة مطلوبة: إن كانت مفقودة، يتوقّف Compose برسالتك بدلًا من تشغيل Postgres بكلمة مرور فارغة.

بيئة الـ container هي ما تراه العملية في الداخل. وتأتي من environment: وenv_file: في الخدمة:

YAML
  api:
    env_file: .env.api
    environment:
      DATABASE_URL: postgres://notes:${POSTGRES_PASSWORD}@db:5432/notes

ملف .env الخاص بالمشروع لا يُمرَّر إلى الـ containers من تلقاء نفسه. إذا كان يجب أن تصل قيمة إلى التطبيق، فأشر إليها تحت environment: أو اذكر الملف تحت env_file:. وحين يأتي المتغيّر نفسه من المصدرين، تفوز environment:.

في الاستبدال، المتغيّر المضبوط في الـ shell يتغلّب على نظيره في .env، لذا فإن API_PORT=9090 docker compose up -d تجاوز سريع لمرة واحدة دون تعديل أي ملف. ولاستخدام ملف مختلف كليًا، مرّر --env-file .env.staging. أبقِ عدد هذه الملفات قليلًا؛ فثلاثة ملفات env متشابهة بفروق صغيرة كفيلة بأن تجعل الفريق يشخّص الإعدادات بدلًا من الكود.

احفظ في المستودع ملف .env.example بقيم وهمية، وأضف .env إلى .gitignore. ينسخ الزملاء الجدد ملف المثال ويملؤونه.

أسرار Compose

متغيّرات البيئة مرئية في docker inspect ولكل عملية داخل الـ container. لكلمات المرور، يستطيع Compose تركيب ملف بدلًا من ذلك:

YAML
services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

يظهر السرّ داخل الـ container في /run/secrets/db_password. تقرأ image ‏Postgres المتغيّر POSTGRES_PASSWORD_FILE، وكثير من الـ images الرسمية تدعم عرف _FILE نفسه. أما في تطبيقك، فاقرأ الملف عند البدء. الـ Kubernetes Secrets المركّبة كملفات تعمل بالطريقة نفسها، فتنتقل العادة معك.

ملف أساسي مع override

يدمج Compose تلقائيًا compose.override.yaml فوق compose.yaml إن وُجد. استخدم هذا الفصل عن قصد:

  • compose.yaml: ما تحتاج إليه كل بيئة، بما فيها الـ CI.
  • compose.override.yaml: وسائل الراحة للمطوّرين، مثل منافذ قاعدة البيانات المنشورة، وخيارات التشخيص، وقواعد المراقبة.

يستطيع الـ CI عندها التشغيل بالملف الأساسي وحده: docker compose -f compose.yaml up -d --wait. حين تمرّر -f، لا يُحمَّل الـ override تلقائيًا.

الـ Profiles تغطّي الخدمات الاختيارية. container ‏pgAdmin لا يريده إلا بعض الناس يحصل على profiles: [tools]، ولا يبدأ إلا مع docker compose --profile tools up.

وضع Watch: حلقة تعديل سريعة

إعادة بناء الـ image مع كل تعديل بطيئة. وربط المشروع كاملًا بـ bind mount هشّ: ‏node_modules على المضيف (المبني لـ macOS) يحجب نظيره في الـ container (المبني لـ Linux)، والوصول إلى الملفات عبر آلة Docker Desktop الافتراضية بطيء. Compose watch يحلّ المشكلتين بمزامنة الملفات التي تختارها فقط إلى الـ container الذي يعمل.

compose.override.yaml
services:
  api:
    build:
      context: .
      target: build
    command: ["npm", "run", "dev"]
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package.json
        - action: rebuild
          path: package-lock.json

للتطوير، يبني الـ override مرحلة build (التي تحتوي على اعتماديات التطوير) ويشغّل npm run dev، وهو في notes-api ‏tsx watch src/server.ts، أداة لتشغيل TypeScript تعيد التشغيل عند التغييرات. ثم:

Terminal
docker compose up --watch

عدّل src/routes.ts فيُنسخ الملف إلى /app/src خلال ثانية؛ ويعيد tsx تشغيل الخادم. غيّر package-lock.json فيعيد Compose بناء الـ image ويستبدل الـ container، لأن الاعتماديات الجديدة تحتاج إلى تثبيت حقيقي. وهناك إجراء ثالث، sync+restart، يزامن الملفات ويعيد تشغيل الـ container، ويناسب ملفات الإعدادات التي لا يقرؤها التطبيق إلا عند البدء. والأمر docker compose watch يفعل ما يفعله up --watch مع مخرجات أهدأ.

اكتمل القسم الثالث: بيئة محلية قابلة للتكرار، ببيانات دائمة، وإقلاع مرتّب، وإعدادات آمنة، وحلقة تعديل سريعة. في القسم التالي ستأخذ الـ image نفسها إلى Kubernetes، بدءًا بطريقة تفكير Kubernetes.

أهم النقاط

  • ملف .env الخاص بالمشروع يغذّي استبدال ${VAR} داخل compose.yaml؛ بينما env_file: وenvironment: تضبطان المتغيّرات داخل الـ containers. إنهما آليتان مختلفتان.
  • الصيغة ${VAR:?message} تجعل Compose يفشل سريعًا برسالة واضحة حين تكون قيمة مطلوبة مفقودة.
  • الأمر docker compose config يطبع الملف بعد الدمج والاستبدال الكاملين، وهو أسرع طريقة لتشخيص الإعدادات.
  • الملف compose.override.yaml يُدمج تلقائيًا، لذا أبقِ الإعدادات المشتركة في compose.yaml وإعدادات التطوير وحدها في الـ override.
  • الخيار develop.watch مع sync ينسخ الملفات المعدّلة إلى الـ container الذي يعمل، ومع rebuild يعيد بناء الـ image حين تتغيّر الاعتماديات.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

ملف .env في مشروعك يحتوي على LOG_LEVEL=debug. خدمة api ليس فيها مدخلات environment: ولا env_file:. ما قيمة LOG_LEVEL داخل الـ container؟

السؤال 2 من 4

الملف compose.yaml يحتوي على POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set it in .env} والمتغيّر غير معرّف في أي مكان. ماذا يحدث عند docker compose up؟

السؤال 3 من 4

تعدّل src/routes.ts. مع قواعد المراقبة أدناه، ماذا يفعل Compose؟

YAML
develop:
  watch:
    - action: sync
      path: ./src
      target: /app/src
    - action: rebuild
      path: package-lock.json
السؤال 4 من 4

تريد خدمة pgadmin متاحة للتشخيص المحلي، لكن يجب ألا تبدأ في الـ CI أو عند الزملاء الذين لا يحتاجون إليها. ماذا تستخدم؟

تدريب

املأ الفراغات

قيم مطلوبة وحلقة تعديل سريعة

املأ الفراغات حتى يرفض Compose البدء حين يكون POSTGRES_PASSWORD مفقودًا، وتُنسخ تعديلات الكود إلى container الـ API الذي يعمل، وتعيد تغييرات الـ lockfile بناء الـ image، وتبدأ البيئة في وضع المراقبة.

# compose.yaml services: db: environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORDset POSTGRES_PASSWORD in .env} # compose.override.yaml services: api: develop: watch: - action: path: ./src target: /app/src - action: path: package-lock.json # start the stack and keep watching: # docker compose up