> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# بيئة التطوير المحلية: كيف تحاكي البريد وDNS وHTTPS على جهازك
- URL: https://arabroot.io/articles/بيئة-التطوير-المحلية-البريد-و-dns-و-https/
- Published: 2026-10-12T21:00:00.000Z
- Updated: 2026-10-12T11:55:52.000Z
- Description: لماذا تستبدل البريد وDNS وHTTPS ببدائل محلية أثناء التطوير، وكيف يعامل المتصفح localhost، وأي النطاقات تتجنب.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, Docker والحاويات, الاستضافة الذاتية

عندما تكتب ميزة «نسيت كلمة المرور» في تطبيقك فإن الكود لا يعمل وحده، فهو يرسل بريداً عبر سيرفر SMTP، ويفتح رابطاً على نطاق له شهادة HTTPS، وربما يرفع ملفاً إلى تخزين S3، أو ينتظر Webhook من بوابة الدفع، أو يحول المستخدم إلى Google للدخول عبر OAuth. وفي الإنتاج Production كل هذه خدمات حقيقية، أما على جهازك فأنت أمام سؤال يتكرر مع كل مشروع: هل أربط جهازي بالخدمات الحقيقية، أم أشغل بديلاً محلياً يتصرف مثلها؟ وكيف أختبر ميزة تحتاج HTTPS وأنا أعمل على `localhost`؟

وهذا المقال هو الأول في سلسلة من ثلاثة أجزاء عن بيئة التطوير المحلية Local Development Environment، والمواضيع في السلسلة:

- بيئة التطوير المحلية: الفكرة، وما الذي تحاكيه، ولماذا (الموضوع الحالي).
- [أدوات بيئة التطوير المحلية](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AD%D9%84%D9%8A%D8%A9/): صناديق التقاط البريد، وشهادات HTTPS المحلية، وDNS المحلي، ومحاكيات السحابة وال API، وأنفاق ال Webhooks، وTestcontainers وDev Containers.
- [التطبيق العملي](https://arabroot.io/articles/%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D9%85%D9%84%D9%81-docker-compose-%D9%88%D8%A7%D8%AD%D8%AF/): ملف Docker Compose واحد فيه التطبيق وMailpit وCaddy وDNS محلي، واختبار آلي يتأكد أن بريد استعادة كلمة المرور يصل.

ولاحظ أن هذه السلسلة تبدأ من جهاز المطور، أما ما بعده من بيئات dev وstaging وprod وبيئات المعاينة لكل طلب دمج فقد شرحناه في مقال [البيئات وGitOps للفرق](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/)، ولن نكرره هنا.

وسوف نناقش في هذا المقال ما يلي:

- لماذا لا تربط جهاز التطوير بالخدمات الحقيقية، وما الذي حدث عندما وصلت رسالة اختبار إلى مشتركين حقيقيين.
- التطابق مع الإنتاج Dev/Prod Parity مقابل سرعة التطوير، ودرجات المحاكاة من Mock داخل الكود إلى بيئة Sandbox عند المزود.
- خريطة الخدمات الخارجية وما يقابل كل منها على جهازك.
- الأسماء المحلية: `localhost` و`.test` و`.internal`، والأسماء التي تتجنبها مثل `.local` و`.dev`.
- السياق الآمن Secure Context: لماذا تعمل Passkeys وService Workers على `localhost` دون شهادة، ولا تعمل على عنوان IP في الشبكة.
- تجربة عملية تفتح الخادم نفسه بسبعة عناوين وتقيس ما يسمح به المتصفح في كل منها، ثم نكسرها.
- المخاطر: البريد الحقيقي، والمفاتيح الحقيقية في ملف `.env`، وجهة الإصدار المحلية التي تثق بها.

## لماذا لا تستخدم الخدمات الحقيقية من جهازك؟

الحل البسيط الذي يبدأ به أغلب المطورين أن يضعوا في ملف `.env` على أجهزتهم إعدادات حساب SMTP حقيقي، ومفاتيح S3 حقيقية، ومفتاح بوابة الدفع، لأن هذا أسرع طريق لترى الميزة تعمل. وقد يبدو ذلك من الوهلة الأولى منطقياً، فأنت تختبر على الخدمة نفسها التي سوف يستخدمها الإنتاج، لكن في الواقع هذا الإعداد يسبب خمس مشكلات تظهر واحدة بعد الأخرى:

- **الأثر الحقيقي Side Effects:** كل تجربة على ميزة البريد ترسل رسالة حقيقية، وأي خطأ في قائمة المستلمين أو في البيانات التجريبية يصل إلى أشخاص حقيقيين، وكذلك الحال مع رسائل SMS وعمليات الدفع.
- **البيانات المشتركة Shared State:** عندما يشترك فريق كامل في Bucket واحد أو حساب بريد واحد فإن ملفات زميلك ورسائله تختلط بنتائج اختبارك، والاختبار الذي نجح على جهازك يفشل على جهاز غيرك.
- **الحدود والتكلفة Rate Limits and Cost:** المزود يحسب كل رسالة وكل طلب، ويحجب الحساب إذا تجاوزت الحد أو ارتفعت نسبة الرسائل المرتدة Bounces، وبالتالي قد يتوقف بريد الإنتاج بسبب تجارب التطوير إذا كان الحساب واحداً.
- **الأسرار على الأجهزة Secrets Sprawl:** مفتاح حقيقي في ملف `.env` على كل جهاز في الفريق يعني أن تسريب جهاز واحد أو رفع الملف بالخطأ إلى Git يكشف حساب الإنتاج، وقد شرحنا ما يحدث بعد التسريب في مقال [اكتشاف الأسرار المسربة](https://arabroot.io/articles/%D8%A7%D9%83%D8%AA%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%B3%D8%B1%D8%A7%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%B1%D8%A8%D8%A9-%D9%85%D8%B9-gitleaks-%D9%88-trufflehog/).
- **الاعتماد على الشبكة:** الاختبار الذي يحتاج الإنترنت يصبح بطيئاً ومتقلباً Flaky، ويتوقف إذا انقطع الاتصال أو تغيرت استجابة المزود.

والإجابة الصحيحة أن يبقى الكود كما هو، وأن يتغير العنوان الذي يتحدث معه فقط، فالتطبيق يقرأ عنوان سيرفر SMTP واسم ال Bucket ومفتاح ال API من الإعداد كما ذكرنا في [قسم الإعداد لكل بيئة](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/#config-per-env)، وعلى جهازك يشير هذا الإعداد إلى بديل محلي يتحدث البروتوكول نفسه ولا يصل إلى أحد. والصورة التالية تبين الفكرة:

![في الوسط حاسوب المطور وعليه التطبيق، وعلى اليسار عمود الإنتاج فيه سيرفر SMTP حقيقي وDNS عام وشهادات Let's Encrypt وAWS S3 وبوابة دفع ترسل Webhooks ومزود دخول OAuth، وعلى اليمين عمود الجهاز المحلي فيه Mailpit وأسماء localhost وdnsmasq وجهة إصدار محلية mkcert أو Caddy وMinIO أو SeaweedFS وWireMock مع نفق Webhook وKeycloak أو authentik محلي، وبين العمودين سهم مكتوب عليه نفس البروتوكول وعنوان مختلف](https://arabroot.io/content/images/2026/10/local-dev-env-01-fakes.webp)

الكود لا يعرف إن كان يتحدث مع الخدمة الحقيقية أو مع بديلها المحلي، والفرق كله في الإعداد

في 17 يونيو 2021 وصلت إلى جزء من مشتركي قائمة HBO Max البريدية رسالة عنوانها «Integration Test Email #1» ونصها سطر واحد يقول إن هذا القالب تستخدمه اختبارات التكامل فقط، واعتذرت الشركة في اليوم نفسه وقالت إنها أرسلت رسالة اختبار فارغة بالخطأ إلى جزء من القائمة، كما نقلت [صحيفة Deseret News](https://www.deseret.com/entertainment/2021/6/18/22540063/hbo-max-integration-test-email?ref=arabroot.io). والدرس هنا أن اختبار التكامل كان يملك طريقاً إلى قائمة المشتركين الحقيقية، ولو كان بريد الاختبارات يذهب إلى صندوق التقاط لا يرسل لأحد لما خرجت الرسالة أصلاً.

## التطابق مع الإنتاج أم سرعة التطوير؟

مبدأ [Dev/Prod Parity](https://12factor.net/dev-prod-parity?ref=arabroot.io) من منهجية The Twelve-Factor App يطلب أن تبقى بيئة التطوير قريبة من الإنتاج قدر الإمكان، ويحذر صراحة من استخدام خدمة مختلفة في التطوير، مثل SQLite محلياً وPostgreSQL في الإنتاج، لأن الفروق الصغيرة بين الخدمتين تظهر كأخطاء في الإنتاج فقط. وفي المقابل تريد أنت بيئة تعمل في ثوان، ودون إنترنت، ودون حسابات، ودون أن يؤثر خطؤك على أحد. وقد يتساءل البعض: أليس هذان طلبين متعارضين؟ والإجابة أن المحاكاة درجات، وأن لكل خدمة درجة مناسبة، والصورة التالية تبين هذه الدرجات:

![سلم من أربع درجات من اليمين إلى اليسار: الأولى Mock داخل الكود وهي الأسرع وأبعدها عن الإنتاج، والثانية سيرفر بديل يتحدث البروتوكول نفسه مثل Mailpit وMinIO وWireMock، والثالثة البرنامج الحقيقي نفسه في Container مثل PostgreSQL وKeycloak، والرابعة بيئة Sandbox عند المزود مثل وضع الاختبار في بوابة الدفع وهي الأقرب للإنتاج والأبطأ، وتحت السلم سهمان أحدهما للسرعة والعزل والآخر للتطابق مع الإنتاج](https://arabroot.io/content/images/2026/10/local-dev-env-02-ladder.webp)

كلما صعدت درجة اقتربت من الإنتاج، ودفعت ثمن ذلك في السرعة والعزل

1. **بديل داخل الكود Mock أو Fake:** كلاس يحل محل مرسل البريد في اختبار الوحدة Unit Test ويحفظ الرسالة في قائمة، وهو الأسرع، لكنه لا يختبر SMTP ولا الترميز ولا المرفقات، وقد شرحنا الفرق بين Mock وStub وFake في [قسم البدائل في مقال أنواع الاختبارات](https://arabroot.io/articles/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-unit-%D9%88-integration-%D9%88-e2e/#test-doubles).
2. **سيرفر بديل يتحدث البروتوكول نفسه:** مثل Mailpit الذي يستقبل SMTP حقيقياً، أو MinIO وSeaweedFS اللذين يتحدثان واجهة S3، أو WireMock الذي يرد على طلبات HTTP كما يرد ال API الخارجي، وهنا يعمل كود الإرسال الحقيقي ومكتبته كما هي، والذي يتغير هو أن الرسالة لا تخرج.
3. **البرنامج الحقيقي في Container:** قاعدة البيانات نفسها بالإصدار نفسه، أو مزود الهوية نفسه مثل Keycloak أو authentik، وهذا هو الخيار الصحيح لكل خدمة تملكها أنت في الإنتاج، فلا يوجد سبب لمحاكاة PostgreSQL وأنت تستطيع تشغيله.
4. **بيئة Sandbox عند المزود:** مثل وضع الاختبار في بوابة الدفع، وهي الأقرب لسلوك المزود الحقيقي، لكنها تحتاج حساباً وإنترنت، وتتشارك فيها الفريق، وتحتاج طريقاً تصل منه ال Webhooks إلى جهازك.

والقاعدة العملية التي نتبعها في هذا الموقع أن الخدمة التي تملكها وتشغلها في الإنتاج تشغلها كما هي على جهازك بالإصدار نفسه، والخدمة التي تشتريها من مزود خارجي تحاكيها بسيرفر يتحدث بروتوكولها، ثم تختبرها مرة على Sandbox المزود قبل الإصدار، والسبب أن الأولى يمكن مطابقتها بالكامل، والثانية لا يمكن مطابقتها مهما فعلت، فالأفضل أن يكون اختبارها سريعاً ومعزولاً.

## ما الذي تحاكيه لكل خدمة خارجية؟

الجدول التالي يلخص الخدمات التي يحتاجها أغلب تطبيقات الويب، وما يقابل كل منها على جهازك، وما الذي لا يختبره البديل المحلي، والتفاصيل والمقارنة بين الأدوات في [المقال الثاني من السلسلة](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AD%D9%84%D9%8A%D8%A9/):

| الخدمة                 | في الإنتاج                                   | على جهازك                                                        | ما لا يختبره البديل                                  |
| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------- |
| البريد الصادر          | سيرفر SMTP أو خدمة إرسال                     | صندوق التقاط Mail Catcher مثل Mailpit                            | وصول الرسالة فعلاً، وسمعة النطاق، وسجلات SPF وDKIM   |
| أسماء النطاقات         | سجلات حقيقية في DNS عام                      | أسماء تنتهي بـ .localhost أو DNS محلي لنطاق .test                | انتشار السجلات ومدة TTL والتخزين المؤقت عند المزودين |
| شهادات HTTPS           | جهة إصدار عامة مثل Let's Encrypt             | جهة إصدار محلية Local CA مثل mkcert أو Caddy                     | التحقق من ملكية النطاق، وتجديد الشهادات من الخارج    |
| تخزين الملفات          | خدمة AWS S3 أو مزود متوافق                   | سيرفر MinIO أو SeaweedFS أو Garage                               | سياسات IAM الحقيقية وبعض ميزات المزود                |
| الدفع وال Webhooks     | بوابة دفع ترسل Webhooks إلى نطاقك            | وضع الاختبار عند المزود مع نفق Tunnel، أو ردود مسجلة في WireMock | توقيت الإرسال الحقيقي وإعادة المحاولة عند المزود     |
| رسائل SMS              | مزود رسائل                                   | سيرفر HTTP بديل يسجل الطلب بدل إرساله                            | وصول الرسالة إلى الهاتف                              |
| الدخول عبر OAuth وOIDC | حساب Google أو Microsoft أو مزود هوية الشركة | مزود هوية في Container مثل Keycloak أو authentik                 | سياسات المزود الخارجي وشاشة الموافقة عنده            |
| خدمات السحابة          | خدمات مثل SQS وSNS وLambda                   | محاكي مثل LocalStack أو Moto                                     | الأداء والحدود والصلاحيات كما هي عند المزود          |

ولاحظ العمود الأخير، فهو قائمة ما يجب أن تختبره خارج جهازك، في بيئة staging أو Sandbox المزود، لأن البديل المحلي لا يعرف عنه شيئاً، وأوضح مثال على ذلك البريد، فصندوق الالتقاط يخبرك أن تطبيقك أرسل الرسالة الصحيحة إلى العنوان الصحيح بالمحتوى الصحيح، ولا يخبرك هل سوف تصل إلى صندوق الوارد أم إلى الرسائل المزعجة، وهذا موضوع [مصادقة البريد](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-spf-%D9%88-dkim-%D9%88-dmarc-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).

## البريد: صندوق التقاط بدل سيرفر حقيقي

صندوق التقاط البريد Mail Catcher هو سيرفر SMTP يقبل كل رسالة تصله ويحفظها ويعرضها في واجهة ويب، ولا يرسلها إلى أي مستلم، وأشهر أداة لذلك اليوم هي [Mailpit](https://mailpit.axllent.org/?ref=arabroot.io). وقد استخدمناه في عدد من التجارب على هذا الموقع دون أن نشرحه بالتفصيل، فهو «السيرفر الآخر» في [مختبر SMTP وIMAP](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-smtp-%D9%88-imap/#lab-setup)، والسيرفر الذي التقط حملة الاختبار في [دليل listmonk](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-listmonk-%D9%84%D9%84%D9%86%D8%B4%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF%D9%8A%D8%A9/)، ودعوات المستخدمين في [دليل Chatwoot](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-chatwoot-%D9%84%D8%AE%D8%AF%D9%85%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1/)، ورسائل الحجز في [دليل Cal.com](https://arabroot.io/articles/%D8%AD%D8%AC%D8%B2-%D8%A7%D9%84%D9%85%D9%88%D8%A7%D8%B9%D9%8A%D8%AF-%D9%88%D8%A7%D9%84%D8%A7%D8%AC%D8%AA%D9%85%D8%A7%D8%B9%D8%A7%D8%AA-%D9%85%D8%B9-cal-com/). والفكرة في كل هذه التجارب واحدة: التطبيق يرسل عبر SMTP كما يرسل في الإنتاج، والفرق أن العنوان هو `mailpit:1025` بدل سيرفر المزود.

والأهم من الواجهة أن صندوق الالتقاط يملك API تقرأ منه الاختبارات الآلية، فبدل أن تفتح الواجهة وتتأكد بعينك أن رسالة استعادة كلمة المرور وصلت، يكتب الاختبار طلباً إلى ال API ويبحث عن الرسالة ويستخرج الرابط منها ويفتحه، وهذا ما سوف نبنيه في [الجزء الثالث](https://arabroot.io/articles/%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D9%85%D9%84%D9%81-docker-compose-%D9%88%D8%A7%D8%AD%D8%AF/). أما شرح طريق الرسالة في SMTP نفسه فموجود في [مقال كيف يعمل البريد الإلكتروني](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-smtp-%D9%88-imap/)، وشرح البريد الذي يرسله التطبيق في الإنتاج موجود في [مقال Transactional Email](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-transactional-email/).

## الأسماء المحلية: أي نطاق تستخدم على جهازك؟

أغلب المطورين يبدأون بـ `localhost:3000` و`localhost:8025`، وهذا يكفي لتطبيق واحد، لكنه يتعب عندما يكون لديك تطبيق وواجهة إدارة وسيرفر بريد وتخزين، فكل منها على منفذ مختلف، وملفات تعريف الارتباط Cookies لا تفصل بين المنافذ، والسبب أن ال Cookie مرتبط بالاسم وليس بالمنفذ كما شرحنا في [قسم خصائص ال Cookies](https://arabroot.io/articles/%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-%D9%88%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88-jwt-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#cookie-attributes)، وبالتالي تطبيقان على `localhost` بمنفذين مختلفين يريان ملفات بعضهما. والحل أن يكون لكل خدمة اسم، والسؤال: أي اسم؟

هنا تحتاج إلى معرفة أن بعض النطاقات محجوزة بمعايير، فلا يمكن لأحد أن يشتريها ولن تتعارض مع موقع حقيقي، وبعضها يبدو مناسباً لكنه يسبب مشكلات:

| النطاق                               | حالته                                                                                                                                                                                                                                         | هل يناسب التطوير؟                                                                                 |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| localhost وكل ما ينتهي بـ .localhost | محجوز في [RFC 6761](https://www.rfc-editor.org/rfc/rfc6761.html?ref=arabroot.io)، ويشير دائماً إلى جهازك نفسه Loopback                                                                                                                        | نعم، وهو الخيار الأول لأن المتصفح يعامله كسياق آمن دون شهادة                                      |
| .test                                | محجوز للاختبار في [RFC 2606](https://www.rfc-editor.org/rfc/rfc2606.html?ref=arabroot.io) وRFC 6761، ولا يباع                                                                                                                                 | نعم، عندما تحتاج اسماً يشير إلى غير جهازك، مثل Container أو جهاز آخر في الشبكة، ويحتاج DNS محلياً |
| .example وexample.com                | محجوزة للتوثيق والأمثلة                                                                                                                                                                                                                       | للأمثلة وعناوين البريد التجريبية فقط                                                              |
| .internal                            | حجزته ICANN في [قرار مجلسها في 29 يوليو 2024](https://www.icann.org/en/board-activities-and-meetings/materials/approved-resolutions-special-meeting-of-the-icann-board-29-07-2024-en?ref=arabroot.io) للاستخدام الخاص، ولن يضاف إلى DNS العام | مناسب للشبكات الداخلية في الشركة أكثر من جهاز المطور                                              |
| .local                               | مخصص لبروتوكول mDNS في [RFC 6762](https://www.rfc-editor.org/rfc/rfc6762.html?ref=arabroot.io)، الذي تعلن به الطابعات والأجهزة عن نفسها                                                                                                       | لا، لأن الأنظمة ترسل أسئلته إلى mDNS وليس إلى DNS العادي، فتتأخر الإجابة أو تفشل                  |
| .dev                                 | نطاق حقيقي يباع تديره Google Registry، وكله على قائمة [HSTS Preload](https://www.registry.google/domains/dev/?ref=arabroot.io)                                                                                                                | لا، فالمتصفح يرفض أي اتصال به عبر HTTP، كما سوف نرى في التجربة                                    |
| نطاق شركتك الحقيقي                   | مسجل باسمك                                                                                                                                                                                                                                    | نعم إذا كنت تملكه وتديره، مثل dev.example-company.com، ولا تستخدم أبداً نطاقاً لا تملكه           |

ويجدر الإشارة هنا إلى أن RFC 6761 يقول إن مكتبات تحويل الأسماء Name Resolution APIs «SHOULD» تعامل أسماء `localhost` معاملة خاصة وتعيد عنوان Loopback دائماً، وكلمة SHOULD في المعايير تعني «ينبغي» وليس «يجب»، ولهذا لا تتصرف كل البرامج على جهازك بالطريقة نفسها مع `app.localhost`، وهذا ما سوف تراه بعينك في التجربة. أما ملف `/etc/hosts` فيربط أسماء كاملة بعناوين، ولا يفهم النجمة Wildcard، فلا يمكنك أن تكتب فيه `*.lab.test` وتتوقع أن يعمل كل اسم تحته، ولهذا تحتاج DNS محلياً عندما يكون لديك أسماء كثيرة، وهذا موضوع المقال الثاني.

## لماذا تعمل Passkeys على localhost دون شهادة؟

المتصفحات الحديثة تقصر عدداً من الميزات على ما يسمى [السياق الآمن Secure Context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure%5FContexts?ref=arabroot.io)، أي الصفحات التي وصلت عبر HTTPS، والسبب أن هذه الميزات خطيرة إذا استطاع أحد في الطريق أن يعدل الصفحة، ومنها:

- واجهة [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service%5FWorker%5FAPI?ref=arabroot.io) التي تعمل في الخلفية وتعترض طلبات الصفحة، والتي تحتاجها [الإشعارات الفورية Push Notifications](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D8%A7%D8%B4%D8%B9%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%81%D9%88%D8%B1%D9%8A%D8%A9-push-notifications/) والتطبيقات التي تعمل دون إنترنت.
- واجهة WebAuthn التي تقوم عليها [Passkeys](https://arabroot.io/articles/%D8%A7%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%AF%D9%88%D9%86-%D9%83%D9%84%D9%85%D8%A9-%D9%85%D8%B1%D9%88%D8%B1-passkeys-%D9%88-fido2/)، وواجهة التشفير `crypto.subtle`، والكاميرا والميكروفون في [WebRTC](https://arabroot.io/articles/webrtc-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84-%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD/).
- ملفات تعريف الارتباط بالخاصية `Secure`، فبحسب [توثيق Set-Cookie في MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie?ref=arabroot.io) لا يستطيع موقع على `http:` أن يضع Cookie فيه `Secure`، إلا إذا كان الموقع هو `localhost`.

وقد يتساءل البعض: إذا كانت هذه الميزات تحتاج HTTPS، فكيف نطور عليها دون شهادة؟ والإجابة في خوارزمية «هل هذا الأصل موثوق؟» في [معيار Secure Contexts من W3C](https://w3c.github.io/webappsec-secure-contexts/?ref=arabroot.io)، فهي تعتبر الأصل Origin موثوقاً إذا كان عنوانه في `127.0.0.0/8` أو `::1`، أو إذا كان اسمه `localhost` أو ينتهي بـ `.localhost`، بشرط أن يضمن المتصفح أن هذه الأسماء لا تذهب أبداً إلى غير جهازك. والسبب أن الخطر الذي يحمي منه HTTPS هو أن يعدل أحد الصفحة في الطريق، والطلب إلى Loopback لا يخرج من جهازك أصلاً، فلا يوجد طريق يقف فيه أحد. ولاحظ الشرط الأخير، فالمعيار لا يثق بالاسم لأنه يشير إلى `127.0.0.1` اليوم، وإنما لأن المتصفح نفسه يضمن أنه لن يشير إلى غيره أبداً، وهذا يعني أن اسماً مثل `dev.lab.test` لا يصبح آمناً حتى لو كتبته في ملف hosts ليشير إلى `127.0.0.1`. والصورة التالية تلخص ذلك:

![جدول مرسوم فيه سبعة عناوين للخادم نفسه، أمام كل عنوان علامة خضراء أو حمراء: http://127.0.0.1 وhttp://localhost وhttp://app.localhost وhttp://shop.app.localhost بعلامة خضراء، وhttp://172.17.0.2 وhttp://dev.lab.test بعلامة حمراء مع ملاحظة أن الثاني يشير إلى 127.0.0.1 ومع ذلك ليس آمناً، وhttps://app.lab.test بشهادة من جهة إصدار محلية موثوقة بعلامة خضراء، وفي الأعلى أيقونات الميزات التي تفتح في السياق الآمن: Service Worker وSecure Cookie وcrypto.subtle وWebAuthn](https://arabroot.io/content/images/2026/10/local-dev-env-03-secure-context.webp)

الاسم هو الذي يحدد السياق الآمن، وليس العنوان الذي يشير إليه في ملف hosts

## تجربة عملية: الخادم نفسه بسبعة عناوين

الآن سوف نختبر الكلام السابق بأنفسنا، فنكتب خادماً صغيراً بلغة Node.js دون أي شهادة، يضع في كل استجابة Cookie بالخاصية `Secure`، ويعرض صفحة تسأل المتصفح عن الميزات المتاحة، ثم نفتحه بسبعة عناوين في متصفح Chromium نظيف في كل مرة. والتجربة كلها داخل Container واحد، فلا نغير أي شيء على جهازك، ولا نلمس ملف hosts الخاص بك، والمتصفح نفسه يعمل بملف شخصي Profile مؤقت يحذف مع ال Container.

الخادم في الملف `server.mjs`:

```javascript
// One tiny HTTP server (no TLS) that we open under several names to see how the browser treats each origin.
import { createServer } from 'node:http';

const page = `<!doctype html><meta charset="utf-8"><title>probe</title>
<script>
window.probe = async () => ({
  origin: location.origin,
  isSecureContext: window.isSecureContext,
  serviceWorker: 'serviceWorker' in navigator,
  cryptoSubtle: !!(window.crypto && crypto.subtle),
  webauthn: typeof PublicKeyCredential !== 'undefined',
  secureCookieKept: document.cookie.includes('probe=1'),
});
</script>`;

createServer((req, res) => {
    res.setHeader('Set-Cookie', 'probe=1; Secure; Path=/; SameSite=Lax');
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.end(page);
}).listen(8080, '0.0.0.0', () => console.log('probe server on :8080'));
```

والملف `probe.mjs` يفتح كل عنوان في سياق متصفح جديد Browser Context حتى لا تنتقل ال Cookies من عنوان إلى آخر، ويطبع ما وجده:

```javascript
import { chromium } from 'playwright';

// myapp.dev is mapped to 127.0.0.1 inside this browser only, to see what the HSTS preload list does
const browser = await chromium.launch({ args: ['--host-resolver-rules=MAP myapp.dev 127.0.0.1'] });
console.log('chromium', browser.version());
for (const url of process.argv.slice(2)) {
    const ctx = await browser.newContext();            // a new, empty profile for every origin
    const page = await ctx.newPage();
    try {
        await page.goto(url, { timeout: 8000 });
        console.log(JSON.stringify(await page.evaluate(() => window.probe())));
    } catch (e) {
        console.log(JSON.stringify({ url, error: e.message.split('\n')[0] }));
    }
    await ctx.close();
}
await browser.close();
```

نشغل الخادم في Container من Docker Image مبنية على `mcr.microsoft.com/playwright:v1.64.0-noble` ومعها مكتبة Playwright 1.64.0، وهي نفس ال Image التي سوف نستخدمها في الجزء الثالث وتجد ملف بنائها هناك، ونضيف داخل ال Container فقط الاسم `dev.lab.test` ليشير إلى `127.0.0.1`:

```bash
docker run -d --name lab-ldev-probe -v "$PWD:/tests/probe:ro" \
  --add-host dev.lab.test:127.0.0.1 lab-ldev-pw:1 node /tests/probe/server.mjs

docker exec -w /tests/probe lab-ldev-probe node probe.mjs \
  http://127.0.0.1:8080/ http://localhost:8080/ http://app.localhost:8080/ \
  http://shop.app.localhost:8080/ http://172.17.0.2:8080/ \
  http://dev.lab.test:8080/ http://myapp.dev:8080/
```

والمخرج سوف يكون كما يلي:

```text
chromium 156.0.8078.4
{"origin":"http://127.0.0.1:8080","isSecureContext":true,"serviceWorker":true,"cryptoSubtle":true,"webauthn":true,"secureCookieKept":true}
{"origin":"http://localhost:8080","isSecureContext":true,"serviceWorker":true,"cryptoSubtle":true,"webauthn":true,"secureCookieKept":true}
{"origin":"http://app.localhost:8080","isSecureContext":true,"serviceWorker":true,"cryptoSubtle":true,"webauthn":true,"secureCookieKept":true}
{"origin":"http://shop.app.localhost:8080","isSecureContext":true,"serviceWorker":true,"cryptoSubtle":true,"webauthn":true,"secureCookieKept":true}
{"origin":"http://172.17.0.2:8080","isSecureContext":false,"serviceWorker":false,"cryptoSubtle":false,"webauthn":false,"secureCookieKept":false}
{"origin":"http://dev.lab.test:8080","isSecureContext":false,"serviceWorker":false,"cryptoSubtle":false,"webauthn":false,"secureCookieKept":false}
{"url":"http://myapp.dev:8080/","error":"page.goto: net::ERR_SSL_PROTOCOL_ERROR at http://myapp.dev:8080/"}
```

في المخرج أعلاه لاحظ التالي:

- العناوين الأربعة الأولى سياق آمن دون أي شهادة، وفيها Service Worker وWebAuthn و`crypto.subtle`، والمتصفح احتفظ بال Cookie الذي يحمل `Secure` رغم أن الاتصال HTTP عادي، وهذا هو الاستثناء الذي يسمح لك بتطوير Passkeys على `app.localhost` كما فعلنا في [تجربة Passkeys](https://arabroot.io/articles/%D8%A7%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%AF%D9%88%D9%86-%D9%83%D9%84%D9%85%D8%A9-%D9%85%D8%B1%D9%88%D8%B1-passkeys-%D9%88-fido2/#demo).
- الاسم `shop.app.localhost` بمستويين يعمل أيضاً، وهذا مفيد للتطبيقات متعددة المستأجرين Multi-tenant التي تعطي كل عميل نطاقاً فرعياً.
- العنوان `172.17.0.2` هو عنوان ال Container في شبكة Docker، وهو يمثل ما يحدث عندما تفتح تطبيقك من جهاز آخر أو من هاتفك عبر عنوان الشبكة، فكل الميزات اختفت، ولم يحفظ المتصفح ال Cookie، وتطبيقك الذي يحفظ الجلسة في Cookie آمن لن يسجل دخول أحد.
- الاسم `dev.lab.test` يشير إلى `127.0.0.1` نفسه، ومع ذلك ليس سياقاً آمناً، وهذا ما ذكرناه قبل قليل: المتصفح يثق بالاسم الذي يضمن هو أنه محلي، وليس بما يقوله ملف hosts.

### نكسرها: النطاق .dev والأسماء التي لا تفهمها كل البرامج

السطر الأخير في المخرج هو الكسر الأول، فقد جعلنا المتصفح نفسه يحول `myapp.dev` إلى `127.0.0.1`، وطلبنا `http://` صراحة، لكن Chromium لم يرسل طلب HTTP أصلاً، وإنما حول الطلب إلى HTTPS قبل أن يخرج لأن النطاق `.dev` كله في قائمة HSTS Preload المدمجة في المتصفح، ثم فشل لأن الخادم لا يتحدث TLS، وهذا هو الخطأ `ERR_SSL_PROTOCOL_ERROR` الذي يراه كل من يختار `.dev` لتطويره المحلي. ولا يوجد إعداد في الخادم يصلح ذلك، والحل الوحيد ألا تستخدم `.dev` إلا إذا كنت تملك النطاق وتريد HTTPS.

والكسر الثاني أن `app.localhost` لا يعمل في كل برنامج على جهازك، فالمعيار يقول «SHOULD» كما ذكرنا، والأمر التالي يسأل ثلاث أدوات مختلفة داخل ال Container نفسه:

```bash
docker exec lab-ldev-probe sh -c '
  getent hosts app.localhost || echo "getent: app.localhost not found (exit $?)"
  node -e "require(\"dns\").lookup(\"app.localhost\",(e,a)=>console.log(e?e.code:a))"
  curl -sv http://app.localhost:8080/ -o /dev/null 2>&1 | grep -E "Trying|Connected"'
```

والمخرج سوف يكون كما يلي:

```text
getent: app.localhost not found (exit 2)
EAI_AGAIN
*   Trying [::1]:8080...
*   Trying 127.0.0.1:8080...
* Connected to app.localhost (127.0.0.1) port 8080
```

في المخرج أعلاه لاحظ التالي:

- الأمر `getent` يستخدم مكتبة glibc 2.39 في Ubuntu 24.04 مباشرة، وهي هنا لا تعرف `app.localhost` لأن ملف `/etc/hosts` فيه `localhost` وحده، ولا يوجد في ال Container خدمة systemd-resolved التي تعيد Loopback لكل اسم ينتهي بـ `.localhost`.
- وبيئة Node.js تستخدم المكتبة نفسها في `dns.lookup`، فأرسل السؤال إلى DNS وفشل بالخطأ `EAI_AGAIN`، وهذا يعني أن الكود الذي يتصل من الخادم إلى `api.localhost` مثلاً لن يعمل هنا، حتى لو عمل الاسم نفسه في المتصفح.
- أداة curl 8.5.0 تعامل `.localhost` بنفسها دون أن تسأل النظام كما يشرح [دليل curl](https://everything.curl.dev/usingcurl/connections/name.html?ref=arabroot.io)، فاتصلت ب `::1` ثم `127.0.0.1`، وChromium يفعل الشيء نفسه.

والخلاصة من هذا الكسر أن `*.localhost` ممتاز لما يفتحه المتصفح، لكنه لا يصلح اسماً تتصل به خدمة من خدمة أخرى، وعندما تحتاج أسماء تعمل في المتصفح وفي الكود وفي ال Containers معاً تحتاج DNS محلياً يجيب عن نطاق مثل `lab.test`، ثم تحتاج HTTPS عليه لأنه ليس سياقاً آمناً كما رأينا.

ولإزالة التجربة:

```bash
docker rm -f lab-ldev-probe
```

## متى لا يكفيك localhost وتحتاج HTTPS محلياً؟

إذا كان تطبيقك يعمل كله على `localhost` وتفتحه من متصفح جهازك فقط، فلا تحتاج أي شهادة، وهذا أبسط إعداد وأكثرها أماناً. لكن هناك حالات تحتاج فيها HTTPS حقيقياً على جهازك، وأهمها:

- **الاختبار من جهاز آخر:** فتح التطبيق من هاتفك أو من جهاز زميل عبر عنوان الشبكة، وقد رأينا في التجربة أن السياق الآمن يختفي.
- **الخدمات تتصل ببعضها بالأسماء:** كما رأينا في الكسر الثاني، فالاسم الذي تستخدمه الخدمات بينها يجب أن يكون اسماً يجيب عنه DNS، وليس `.localhost`.
- **ال Cookies العابرة للمواقع:** ال Cookie بالخاصية `SameSite=None` يجب أن يحمل `Secure`، وهذا يظهر عندما يكون للواجهة وال API اسمان مختلفان.
- **السلوك المرتبط بالبروتوكول:** المحتوى المختلط Mixed Content، والتحويل من HTTP إلى HTTPS، وترويسة HSTS، والترويسات التي يضيفها ال Reverse Proxy، وكلها أخطاء لا تظهر إلا بعد النشر إذا كنت تطور على HTTP، وقد جمعنا أكثرها في [مقال تطبيقك خلف Reverse Proxy](https://arabroot.io/articles/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-%D8%AE%D9%84%D9%81-reverse-proxy/).
- **مزودو الدخول OAuth:** بعض المزودين يقبلون عنوان العودة Redirect URI على `http://localhost` فقط، ويطلبون HTTPS لأي اسم آخر، وتفاصيل التدفق في [مقال OAuth وOpenID Connect](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).

وفي هذه الحالات تنشئ جهة إصدار محلية Local CA على جهازك، وتصدر منها شهادة لأسمائك المحلية، ثم تجعل المتصفح يثق بها، وهنا يبدأ الجزء الذي يحتاج حذراً، لأنك تضيف جهة إصدار جديدة إلى قائمة الجهات التي يثق بها جهازك، وقد شرحنا سلسلة الثقة ومخزن الثقة Trust Store في [مقال الشهادات الرقمية](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/#chain-of-trust).

## المخاطر: ما الذي قد يخرج من جهازك؟

بيئة التطوير المحلية تبدو مكاناً لا يحتاج حماية لأنها على جهازك، لكن هناك أربعة أخطاء تتكرر، وكل منها يخرج شيئاً من جهازك إلى العالم:

1. **بريد حقيقي من بيئة التطوير:** كما في قصة HBO Max، والحماية هنا طبقتان: أن يرفض التطبيق نفسه أن يعمل في وضع التطوير بإعداد SMTP غير صندوق الالتقاط، وأن تكون الشبكة التي يعمل عليها لا تصل إلى الإنترنت أصلاً، وسوف نبني الطبقتين ونكسرهما في الجزء الثالث.
2. **مفاتيح حقيقية في ملف `.env`:** ملف الإعداد المحلي يجب ألا يحتوي إلا قيماً محلية، ومفاتيح Sandbox عند الحاجة، ولا يرفع إلى Git، وإذا رفع مفتاح بالخطأ فاعتبره مكشوفاً وغيره فوراً، والسبب أن حذفه من آخر Commit لا يحذفه من التاريخ.
3. **جهة إصدار محلية تثق بها:** المفتاح الخاص لجهة الإصدار المحلية يستطيع أن يصدر شهادة لأي موقع، مثل بنكك أو بريدك، وسوف يقبلها متصفحك، ولهذا يحذر [توثيق mkcert](https://github.com/FiloSottile/mkcert?ref=arabroot.io) من أن الملف `rootCA-key.pem` يعطي من يملكه قدرة كاملة على اعتراض الاتصالات الآمنة من جهازك، فلا تشاركه ولا تضعه في مستودع الفريق، ولكل مطور جهة إصدار خاصة به.
4. **خادم التطوير مفتوح للشبكة:** خادم التطوير الذي يستمع على `0.0.0.0` يراه كل من على شبكة المقهى أو الفندق، ومعه لوحة صندوق البريد وقاعدة البيانات، فاربط الخدمات ب `127.0.0.1` كما شرحنا في [الدرجة الأولى من مقال حماية الأدوات الداخلية](https://arabroot.io/articles/%D9%83%D9%84-%D8%A7%D9%84%D8%B7%D8%B1%D9%82-%D9%84%D8%AD%D9%85%D8%A7%D9%8A%D8%A9-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%88-%D8%A7%D8%AF%D8%A7%D8%A9-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A%D8%A9/#dont-expose)، وعندما تفتح نفقاً Tunnel لتستقبل Webhook تذكر أن النفق ينشر تطبيقك على الإنترنت كله كما ذكرنا في [مقال الأنفاق](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%88-%D8%A7%D9%84%D9%86%D9%81%D9%82-tunnel-%D9%81%D9%8A-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA/#reverse-tunnels)، فأغلقه عندما تنتهي.

## أخطاء شائعة

- **اختبار البريد بحساب Gmail الشخصي:** الرسائل تخرج فعلاً، والحساب قد يحجب، وكلمة مرور التطبيق في ملف `.env`، والحل صندوق التقاط.
- **استخدام `.dev` أو `.local` للتطوير:** الأول يفرض HTTPS بقائمة HSTS، والثاني يذهب إلى mDNS، والبديل `.localhost` أو `.test`.
- **توقع أن يعمل `app.localhost` في كل مكان:** يعمل في المتصفح وcurl، ولا يعمل بالضرورة في الكود الذي يستخدم مكتبة النظام.
- **استخدام Mock لكل شيء:** محاكاة PostgreSQL أو Redis وأنت تستطيع تشغيلهما في Container تخفي أخطاء لا تظهر إلا في الإنتاج.
- **بديل محلي دون اختبار عند المزود:** صندوق الالتقاط لا يختبر وصول البريد، ومحاكي السحابة لا يختبر الصلاحيات الحقيقية، فاختبر مرة في staging قبل الإصدار.
- **مشاركة جهة الإصدار المحلية بين الفريق:** مفتاح واحد على أجهزة الجميع يعني أن تسريب جهاز واحد يكفي لاعتراض اتصالات الفريق كله.

## الخلاصة

- الكود يبقى كما هو بين جهازك والإنتاج، والإعداد وحده يحدد هل يتحدث مع الخدمة الحقيقية أم مع بديلها المحلي.
- الخدمة التي تشغلها في الإنتاج تشغلها نفسها محلياً، والخدمة التي تشتريها تحاكيها بسيرفر يتحدث بروتوكولها، ثم تختبرها مرة عند المزود.
- صندوق التقاط البريد مثل Mailpit يستقبل SMTP حقيقياً ولا يرسل لأحد، ويملك API تقرأ منه الاختبارات الآلية.
- الأسماء المناسبة للتطوير هي `.localhost` للمتصفح و`.test` عندما تحتاج DNS، وتجنب `.dev` و`.local`.
- المتصفح يعامل `localhost` و`*.localhost` و`127.0.0.1` كسياق آمن دون شهادة، وفي التجربة لم يكن `dev.lab.test` آمناً رغم أنه يشير إلى `127.0.0.1`.
- تحتاج HTTPS محلياً عندما تفتح التطبيق من جهاز آخر، أو تتصل الخدمات ببعضها بالأسماء، أو تختبر سلوكاً مرتبطاً بالبروتوكول، ومفتاح جهة الإصدار المحلية هو أخطر ملف على جهازك بعد ذلك.

وفي [المقال التالي](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AD%D9%84%D9%8A%D8%A9/) بإذن الله سوف نقارن الأدوات التي تحقق كل ذلك، من صناديق التقاط البريد إلى Testcontainers وDev Containers، ثم نجمعها في [الجزء الثالث](https://arabroot.io/articles/%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D9%85%D9%84%D9%81-docker-compose-%D9%88%D8%A7%D8%AD%D8%AF/) في ملف Docker Compose واحد.

[![](https://arabroot.io/content/images/2026/10/local-dev-tools-cover-cover.webp)دليلأدوات بيئة التطوير المحلية: ماذا تستخدم لكل خدمة؟مقارنة بالقياس بين أدوات التقاط البريد والشهادات المحلية والأسماء ومحاكاة ال API والسحابة، وأيها تختار لمشروعك.](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AD%D9%84%D9%8A%D8%A9/)[![](https://arabroot.io/content/images/2026/10/local-dev-practice-cover-cover.webp)دليلبيئة تطوير محلية كاملة بملف Docker Compose واحدنبني بيئة تطوير فيها البريد وHTTPS والأسماء المحلية، ونكتب اختباراً آلياً لبريد استعادة كلمة المرور ثم نكسرها.](https://arabroot.io/articles/%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D9%85%D9%84%D9%81-docker-compose-%D9%88%D8%A7%D8%AD%D8%AF/)[![](https://arabroot.io/content/images/2026/10/environments-gitops-cover-cover.webp)دليلمن الفرع إلى الإنتاج: البيئات وGitOps للفرقكيف يختبر فريقك كل ميزة وحدها ويعرضها على العميل دون أن تصل ميزة غير جاهزة إلى الإنتاج.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/)[![](https://arabroot.io/content/images/2026/10/testing-types-cover-cover.webp)دليلأنواع الاختبارات للمبتدئين: من Unit إلى E2E مع Playwrightما الذي يكشفه كل نوع من الاختبارات وما الذي يفوته، وكم اختباراً تكتب من كل نوع، وأين يعمل كل منها في خط CI.](https://arabroot.io/articles/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-unit-%D9%88-integration-%D9%88-e2e/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، والتجربة على Chromium 156.0.8078.4 من Playwright 1.64.0 داخل Ubuntu 24.04 (glibc 2.39) مع Node.js 24.21.0 وcurl 8.5.0 على Docker Engine 29.8.0، والمعلومات من RFC 6761 وRFC 2606 وRFC 6762 ومعيار Secure Contexts وتوثيق MDN وقرار ICANN بشأن `.internal` وصفحة Google Registry عن `.dev` كما قرأناها في 12 أكتوبر 2026.