> ## 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.

# الفرق بين Tiers وLayers: متى يكون نظامك ثلاثي الطبقات فعلاً؟
- URL: https://arabroot.io/articles/المعمارية-متعددة-الطبقات-tiers-و-layers/
- Published: 2026-10-06T15:00:00.000Z
- Updated: 2026-10-09T13:02:47.000Z
- Description: ماذا يعني أن يكون نظامك ثلاثي الطبقات 3-tier فعلاً، والفرق بين الطبقة داخل الكود والمستوى في الشبكة، وما تطلبه الضوابط بالنص.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, هندسة البرمجيات وتصميم الأنظمة, DevOps وCI/CD, الاستضافة الذاتية

في كثير من كراسات الشروط ووثائق المتطلبات الأمنية سوف تجد جملة مثل «يجب أن يكون النظام مبنياً على معمارية ثلاثية الطبقات Three-tier Architecture»، وعندما يسأل المدقق فريق التطوير عن ذلك تأتي إجابات مختلفة تماماً: فريق يقول إن لديه واجهة أمامية Frontend وخلفية Backend وقاعدة بيانات Database فالنظام ثلاثي الطبقات، وفريق آخر يقول إن لديه سيرفرين أحدهما للتطبيق والآخر لقاعدة البيانات، وفريق ثالث يقول إن تطبيقه مكتوب بنمط MVC وهو ثلاثة أجزاء أصلاً، وقد تكون الإجابات الثلاث خاطئة في نظر المدقق مع أن كل فريق صادق فيما يصفه، والسبب أن كلمة «طبقة» في العربية تستخدم لشيئين مختلفين في الإنجليزية: الطبقة Layer التي تصف ترتيب الكود، والمستوى Tier الذي يصف أين يعمل كل جزء وما الذي يفصله عن جاره في الشبكة.

وهذا الخلط ليس مسألة مصطلحات فقط، لأن الضوابط الأساسية للأمن السيبراني ECC التي شرحناها في [الضوابط الأساسية للأمن السيبراني NCA ECC بلغة بسيطة](https://arabroot.io/articles/%D8%A7%D9%84%D8%B6%D9%88%D8%A7%D8%A8%D8%B7-%D8%A7%D9%84%D8%A7%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-%D9%84%D9%84%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B3%D9%8A%D8%A8%D8%B1%D8%A7%D9%86%D9%8A-nca-ecc-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9/) تطلب صراحة «استخدام مبدأ المعمارية متعددة المستويات Multi-tier Architecture» لتطبيقات الويب الخارجية، وضوابط الأنظمة الحساسة CSCC تطلب ألا يقل عدد المستويات عن ثلاثة، وفي [دليل ضوابط أمن التطبيقات](https://arabroot.io/articles/%D8%B6%D9%88%D8%A7%D8%A8%D8%B7-%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AF%D9%84%D9%8A%D9%84-%D9%85%D9%82%D8%AA%D8%B1%D8%AD-%D9%85%D8%B9-%D8%B1%D8%A8%D8%B7-ecc-%D9%88-asvs/) وضعنا لها الضابط `ARC-ASC-02-03`، وكل هذا يحتاج فهماً واضحاً لما يعنيه المستوى قبل أن تبني النظام أو تكتب الرد على المدقق.

وفي هذا المقال سوف نستخدم كلمة «الطبقة» لمصطلح Layer وكلمة «المستوى» لمصطلح Tier، وهي نفس الكلمة التي تستخدمها النسخة العربية من ضوابط الهيئة الوطنية للأمن السيبراني، أما العنوان فيستخدم «متعددة الطبقات» لأنها العبارة التي يبحث بها أغلب الناس. وسوف نناقش في هذا المقال ما يلي:

- الفرق بين الطبقة Layer والمستوى Tier بتعريفات من مصادر معروفة.
- كيف تطور توزيع التطبيقات من الحاسوب المركزي Mainframe إلى Client-Server ثم المعمارية ثلاثية المستويات، ولماذا تغير كل شكل.
- هل تطبيق MVC القديم الذي يبني الصفحات في السيرفر ثلاثي المستويات؟ وماذا عن تطبيقات SPA مع API والخدمات المصغرة Microservices؟
- جدول يفك أشهر صور الخلط: Frontend وBackend، وسيرفران، وContainers على سيرفر واحد.
- ما الذي تطلبه ECC وCSCC بالنص، وكيف يبدو ذلك في الشبكة، وكيف تثبته للمدقق.
- ثمن الفصل، ومتى يكفي سيرفر واحد ومتى تحتاج سيرفرات منفصلة.
- تجربة عملية بثلاث شبكات Docker نرى فيها أن ال Reverse Proxy لا يصل إلى قاعدة البيانات، ثم نكسر التصميم عمداً ونرى الفرق.

## الفرق بين الطبقة Layer والمستوى Tier

الطبقة Layer تقسيم منطقي Logical للكود حسب المسؤولية داخل برنامج واحد، والتقسيم الأشهر ثلاث طبقات: طبقة العرض Presentation Layer التي تستقبل الطلب وتعرض النتيجة، وطبقة منطق العمل Business Logic Layer التي فيها قواعد النظام، وطبقة الوصول إلى البيانات Data Access Layer التي تكلم قاعدة البيانات. ويصف Martin Fowler هذا التقسيم في مقاله [PresentationDomainDataLayering](https://martinfowler.com/bliki/PresentationDomainDataLayering.html?ref=arabroot.io) بأنه من أشهر طرق تقسيم البرامج إلى ثلاث طبقات واسعة: العرض ومنطق المجال والوصول إلى البيانات، ثم يقول بوضوح إن هذه «طبقات منطقية وليست مستويات فعلية»، وإنه يستطيع تشغيل الطبقات الثلاث على حاسوبه المحمول، أو تشغيل العرض والمنطق على جهاز مكتبي وقاعدة البيانات على سيرفر.

أما المستوى Tier فهو فصل فعلي Physical في التشغيل، أي أن كل مستوى يعمل في عملية Process أو جهاز مستقل، وبينه وبين جاره اتصال عبر الشبكة يمكن التحكم فيه بجدار ناري Firewall. وتقول Microsoft في صفحة [N-tier architecture style](https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/n-tier?ref=arabroot.io) إن المعمارية متعددة المستويات تقسم التطبيق إلى طبقات منطقية ومستويات فعلية، وإن الطبقات تفصل المسؤوليات وتدير الاعتماديات، أما المستويات فهي «منفصلة فعلياً وتعمل على أجهزة منفصلة»، وإنك قد تضع كل طبقة في مستواها، لكن هذا ليس شرطاً، فيمكن أن تجمع عدة طبقات في مستوى واحد. وتذكر IBM في شرحها [Three-tier architecture](https://www.ibm.com/think/topics/three-tier-architecture?ref=arabroot.io) نفس الفرق: الطبقة تقسيم وظيفي داخل البرنامج، والمستوى تقسيم وظيفي يعمل على بنية تحتية منفصلة.

والصورة التالية تبين الفرق: في اليمين برنامج واحد فيه ثلاث طبقات تنادي بعضها باستدعاء دالة Function Call داخل نفس العملية، وفي اليسار ثلاثة مستويات على ثلاثة أجهزة بينها شبكة وقواعد جدار ناري:

![في اليمين صندوق منقط بعنوان one deployable فيه ثلاث طبقات ملونة: Presentation layer فيها Controllers وViews، وBusiness logic layer فيها Services، وData access layer فيها Repositories وORM، وبينها أسهم مكتوب عليها function call. وفي اليسار ثلاثة أعمدة Web tier وApp tier وData tier، في كل عمود سيرفر أو قاعدة بيانات، وبينها جدران نارية، وتحتها أسهم HTTP وSQL مكتوب فوقها network call + firewall rule](https://arabroot.io/content/images/2026/10/tiers-layers-03-layers-vs-tiers.webp)

الطبقات ترتب الكود داخل برنامج واحد، والمستويات تفصل أجزاء التشغيل بالشبكة

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

## كيف وصلنا إلى هنا؟ من الحاسوب المركزي Mainframe إلى الخدمات المصغرة Microservices

الفكرة أسهل في الفهم عندما ترى كيف تغير مكان كل جزء من التطبيق عبر العقود، والصورة التالية تلخص ذلك قبل أن نفصل كل مرحلة:

![خط زمني من اليسار إلى اليمين فيه ست بطاقات: 1960s حاسوب مركزي مع طرفيات بمستوى واحد، 1980s جهاز مكتبي يتصل بقاعدة بيانات بمستويين، 1990s جهاز وسيرفر تطبيقات وقاعدة بيانات بثلاثة مستويات، 2000s متصفح وسيرفر MVC وقاعدة بيانات بثلاثة مستويات، 2010s متصفح وAPI بصيغة JSON وقاعدة بيانات بثلاثة مستويات، و2010s+ درع WAF وعدة خدمات وقاعدة بيانات وذاكرة مؤقتة بعدد N من المستويات](https://arabroot.io/content/images/2026/10/tiers-layers-01-timeline.webp)

كل مرحلة حلت مشكلة في التي قبلها، والتواريخ تقريبية

### الحاسوب المركزي والطرفيات الصماء Dumb Terminals: مستوى واحد

في الستينيات والسبعينيات كانت المؤسسات الكبيرة تعمل على حاسوب مركزي Mainframe واحد، مثل عائلة IBM System/360 التي [أعلنتها IBM في 7 أبريل 1964](https://www.ibm.com/history/system-360?ref=arabroot.io)، وكان الموظف يجلس أمام طرفية Terminal لا تعالج شيئاً بنفسها، فهي ترسل ضغطات الأزرار إلى الحاسوب المركزي وتعرض الشاشة التي يرسلها، أما الشاشات ومنطق العمل والبيانات فكلها في جهاز واحد، لذلك يسمى هذا الشكل مستوى واحداً One-tier حتى لو كان الكود داخله مرتباً في طبقات. ونقطة قوته أن كل شيء في مكان واحد تحت سيطرة فريق واحد، ونقطة ضعفه أن الجهاز غال جداً وكل زيادة في المستخدمين تعني حاسوباً مركزياً أكبر.

### Client-Server: مستويان Two-tier

مع انتشار الحواسيب الشخصية في الثمانينيات والتسعينيات صار الجهاز المكتبي قادراً على المعالجة، فانتقلت الشاشات ومنطق العمل إلى برنامج سطح مكتب يسمى العميل الثقيل Fat Client، وبقيت البيانات في سيرفر قاعدة بيانات مشترك، وهذا هو نموذج العميل والخادم Client-Server بمستويين: البرنامج على جهاز الموظف يرسل استعلامات SQL مباشرة إلى قاعدة البيانات. وإذا عملت في شركة قديمة فغالب الظن أنك رأيت برنامج محاسبة أو مستودعات مكتوباً بلغة Delphi أو Visual Basic أو PowerBuilder يتصل بقاعدة SQL Server أو Oracle بهذا الشكل، وما زالت هذه البرامج تعمل في مؤسسات كثيرة.

والحل بسيط ويعمل، لكن له مشكلات تظهر مع الوقت، وهذا هو المهم في قصتنا:

- **بيانات دخول قاعدة البيانات على كل جهاز**: البرنامج يحتاج اسم مستخدم وكلمة مرور لقاعدة البيانات، فهي موجودة في ملف إعدادات أو داخل البرنامج نفسه على كل جهاز في الشركة، ومن يستخرجها يستطيع أن يتصل بقاعدة البيانات بأي أداة ويتجاوز كل قواعد البرنامج.
- **منفذ قاعدة البيانات مفتوح للشبكة كلها**: لأن كل جهاز يحتاج أن يصل إلى المنفذ 1433 أو 5432 أو 1521، فقاعدة البيانات مكشوفة لكل جهاز في الشبكة الداخلية، بما فيها الجهاز المصاب ببرنامج خبيث.
- **الصلاحيات في البرنامج وليست في السيرفر**: البرنامج هو الذي يخفي زر الحذف عن الموظف العادي، لكن قاعدة البيانات نفسها تقبل الحذف من نفس الحساب المشترك.
- **التحديث على كل جهاز**: أي تغيير في منطق العمل يعني تثبيت نسخة جديدة على مئات الأجهزة، وفي الفترة بينهما تعمل نسختان من المنطق على نفس البيانات.

### المعمارية ثلاثية المستويات Three-tier

الحل كان أن ينتقل منطق العمل من جهاز الموظف إلى سيرفر في المنتصف يسمى سيرفر التطبيقات Application Server، فيصبح العميل رقيقاً Thin Client يعرض الشاشات ويرسل الطلبات فقط، وسيرفر التطبيقات هو الوحيد الذي يملك بيانات الدخول إلى قاعدة البيانات ويطبق القواعد والصلاحيات، وقاعدة البيانات لا تقبل الاتصال إلا منه. وتذكر IBM في نفس الصفحة أن ميزة هذا الشكل الأمنية أن مستوى العرض ومستوى البيانات لا يتصلان مباشرة، فيعمل مستوى التطبيق كجدار حماية داخلي يمنع حقن SQL وغيره من الاستغلال إذا صمم جيداً، وأن نموذج المستويين الأصلي يسمح بالاتصال المباشر بين العرض والبيانات.

والصورة التالية تضع الأشكال الثلاثة بجانب بعضها، ولاحظ أن السؤال الأهم في كل شكل هو: من يتصل بقاعدة البيانات؟

![ثلاث لوحات. في اليمين 1 TIER: حاسوب مركزي مكتوب تحته UI logic + business logic + data، ومتصل بثلاث طرفيات Dumb terminals. في الوسط 2 TIERS: جهاز مكتبي Fat client مكتوب تحته UI + business logic + DB password، وسهم SQL على المنفذين 1433 و5432 إلى Database server. في اليسار 3 TIERS: Thin client ثم سهم HTTPS إلى Application server ثم سهم SQL إلى Database server، وسهم أحمر متقطع من العميل إلى قاعدة البيانات عليه علامة منع](https://arabroot.io/content/images/2026/10/tiers-layers-02-one-two-three.webp)

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

## هل تطبيق MVC القديم الذي يبني الصفحات في السيرفر ثلاثي المستويات؟

هذا من أكثر الأسئلة التي تسبب الخلط، والسبب أن MVC أيضاً ثلاثة أجزاء: النموذج Model والعرض View والمتحكم Controller. لكن MVC نمط لتصميم واجهة المستخدم وترتيب الكود، وليس نموذجاً لتوزيع التطبيق على الأجهزة، فقد ابتكره Trygve Reenskaug كما يذكر في [صفحته عن MVC](https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html?ref=arabroot.io) في Xerox PARC عام 1978، وكان هدفه أن يربط بين النموذج الذهني للمستخدم والنموذج الرقمي في الحاسوب، أي أنه وصف لكيف يتعامل المستخدم مع البيانات على الشاشة، ولم يكن عن الشبكات والسيرفرات أصلاً.

وعندما انتقل النمط إلى تطبيقات الويب في أطر مثل Ruby on Rails وASP.NET MVC وLaravel وSpring MVC، صار يصف ترتيب الكود داخل سيرفر التطبيق: ال Controller يستقبل الطلب، وال Model فيه البيانات والقواعد، وال View قالب يبني صفحة HTML. والصورة التالية تبين مكان النمط من المستويات:

![ثلاث مناطق: في اليمين TIER 1 CLIENT فيها متصفح يستلم HTML جاهزاً، وفي الوسط TIER 2 APP SERVER فيها صناديق Controller وModel وView مع خطوات مرقمة: 1 الطلب من المتصفح إلى Controller، 2 من Controller إلى Model، 3 من Model إلى قاعدة البيانات عبر SQL، 4 من Controller إلى View، 5 من View إلى المتصفح كصفحة HTML. وفي اليسار TIER 3 DATA فيها PostgreSQL](https://arabroot.io/content/images/2026/10/tiers-layers-04-mvc-in-tier.webp)

MVC يعيش داخل المستوى الأوسط، والمستويات الثلاثة هي المتصفح وسيرفر التطبيق وقاعدة البيانات

فإجابة السؤال: تطبيق MVC التقليدي يمكن أن يكون ثلاثي المستويات، لكن ليس لأنه MVC، وإنما لأن المتصفح يعمل على جهاز المستخدم، وسيرفر التطبيق على جهاز، وقاعدة البيانات على جهاز أو شبكة لا يصل إليها إلا سيرفر التطبيق. فإذا كانت قاعدة البيانات على نفس السيرفر ومنفذها مفتوح للإنترنت، فالتطبيق ما زال MVC بطبقاته الثلاث، لكنه في نظر الضوابط أقرب إلى مستويين، لأن ما يفصل البيانات عن الإنترنت هو كلمة مرور فقط. وتذكر أن تطبيقاً مكتوباً بلا أي نمط، كملفات PHP يخلط كل منها HTML بالاستعلامات، يمكن أن يكون ثلاثي المستويات إذا نشر بالطريقة الصحيحة، والكود السيئ مشكلة صيانة وأمان أخرى بالطبع.

## تطبيقات SPA مع API: هل تضيف الواجهة مستوى؟

في العقد الأخير انتقل جزء كبير من طبقة العرض إلى المتصفح نفسه، فتطبيق الصفحة الواحدة Single Page Application المكتوب بـ React أو Vue أو Angular هو ملفات JavaScript وHTML ثابتة ينزلها المتصفح من سيرفر ملفات أو شبكة توزيع محتوى CDN، ثم تطلب البيانات بصيغة JSON من واجهة برمجة API في سيرفر التطبيق، وال API هو الذي يكلم قاعدة البيانات، والصورة التالية تبين الخطوات:

![في اليمين متصفح يشغل SPA. سهم 1 إلى Static host / CDN فيه index.html وapp.js ومكتوب files only, no data. سهم 2 من المتصفح إلى API في منطقة APP TIER بطلب GET /api/orders مكتوب تحته fetch() JSON over HTTPS. سهم 3 من ال API إلى Database في منطقة DATA TIER. وفي الأسفل صندوق أحمر يقول إن ملفات ال SPA الثابتة لا تضيف مستوى أمان وإن ال API مكشوف لكل من يعرف عنوانه](https://arabroot.io/content/images/2026/10/tiers-layers-05-spa-api.webp)

الواجهة صارت في المتصفح، لكن المستويات الأمنية ما زالت ال API وقاعدة البيانات وما أمامهما

وهنا خطأ شائع: يظن الفريق أن ال SPA مستوى العرض، وال API مستوى التطبيق، وقاعدة البيانات مستوى البيانات، فالنظام ثلاثي المستويات وانتهى الأمر. والإجابة أن هذا صحيح في وصف أماكن الكود، لكن سيرفر الملفات الثابتة لا يقف بين الإنترنت وال API، فكل من يعرف عنوان ال API يستطيع أن يرسل له أي طلب بدون الواجهة، لذلك لا تضيف ملفات ال SPA حاجزاً أمنياً، وال API هو المستوى المعرض للإنترنت فعلاً، وكل التحقق Validation والصلاحيات Authorization مكانها هناك كما شرحنا في [أمن واجهات API](https://arabroot.io/articles/%D8%A7%D9%85%D9%86-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-api-%D9%85%D8%B9-owasp-api-security-top-10/). والتصميم الذي يحقق المستويات هنا أن يكون أمام ال API بوابة أو Reverse Proxy مع جدار حماية تطبيقات الويب WAF في منطقة معزولة، ثم ال API في شبكة داخلية، ثم قاعدة البيانات في شبكة لا يصل إليها إلا ال API. ويذكر Fowler في نفس المقال أن تقسيم العرض بين عميل غني Rich Client في المتصفح وطبقة خلفية مخصصة له Backend For Frontend في السيرفر هو مجرد طريقة أخرى لتوزيع نفس الطبقات المنطقية.

## المعمارية متعددة المستويات N-tier والخدمات المصغرة Microservices

الرقم ثلاثة ليس حداً، فالأنظمة الكبيرة تضيف مستويات حسب الحاجة: جدار حماية تطبيقات الويب WAF وال Reverse Proxy في المقدمة، ثم مستوى الويب، ثم مستوى أو أكثر لمنطق العمل، ثم الطوابير Queues والذاكرة المؤقتة Cache، ثم قاعدة البيانات، وصفحة Microsoft المذكورة ترسم مثالاً فيه مستويان أوسطان وطابور رسائل بينهما. وتفرق الصفحة بين نموذجين للاتصال: النموذج الصارم Strict الذي يمر فيه الطلب على كل مستوى بالترتيب ولا يتجاوز أي مستوى، والنموذج المرن Relaxed الذي يسمح بتجاوز بعض المستويات، وتذكر أن الصارم أبطأ وأثقل، والمرن يربط الأجزاء ببعضها أكثر فيصعب تغييرها. وهذا يخص الشبكة مباشرة، لأن كل استثناء يسمح بتجاوز مستوى هو قاعدة إضافية في الجدار الناري.

أما الخدمات المصغرة Microservices فهي تقسيم آخر للنظام حسب مجالات العمل، وكل خدمة لها طبقاتها وقاعدة بياناتها غالباً، وكثرة الخدمات لا تعني أن النظام متعدد المستويات بالمعنى الأمني، فإذا كانت الخدمات العشرون وقواعد بياناتها كلها في شبكة واحدة مسطحة Flat Network يصل فيها كل شيء إلى كل شيء، فالمخترق الذي يدخل أي خدمة يصل إلى كل قواعد البيانات. وفي Kubernetes بالذات تذكر [وثائق Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/?ref=arabroot.io) أن ال Pod غير معزول افتراضياً وأن كل الاتصالات الداخلة والخارجة مسموحة، وأن سياسات الشبكة لا تعمل إلا مع إضافة شبكة Network Plugin تدعمها، فوجود Namespace لكل خدمة لا يفصل شيئاً في الشبكة بنفسه، وقد شرحنا متى تحتاج Kubernetes أصلاً في [هل تحتاج Kubernetes فعلاً؟](https://arabroot.io/articles/%D9%87%D9%84-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC-kubernetes-%D9%81%D8%B9%D9%84%D8%A7/).

## فك الخلط: ماذا يعني كل وصف تسمعه؟

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

| ما يقوله الفريق                                                                              | ماذا يعني فعلاً                                                                                             | هل هو ثلاثي المستويات؟                                                                                                                                                           |
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| «عندنا Frontend وBackend وقاعدة بيانات»                                                      | وصف لأجزاء الكود، وقد تكون الثلاثة على سيرفر واحد وفي شبكة واحدة                                            | لا يكفي الوصف للحكم، والسؤال الصحيح: ما الذي يفصل كل جزء عن الآخر في الشبكة؟ ومن يستطيع الوصول إلى منفذ قاعدة البيانات؟                                                          |
| «تطبيقنا MVC»                                                                                | نمط لترتيب الكود داخل سيرفر التطبيق                                                                         | MVC لا يحدد المستويات، فالحكم على طريقة النشر كما شرحنا أعلاه                                                                                                                    |
| «عندنا سيرفران: تطبيق وقاعدة بيانات»                                                         | فصل فعلي للبيانات عن التطبيق، والتطبيق نفسه يستقبل الإنترنت مباشرة                                          | خطوة صحيحة، لكن الجهاز الذي يستقبل الإنترنت هو نفسه الذي يملك بيانات دخول قاعدة البيانات، والقراءة الأحوط أن تضيف مستوى أمامياً في منطقة معزولة DMZ فيه ال Reverse Proxy وال WAF |
| «كل جزء في Container مستقل على نفس السيرفر»                                                  | عزل في العمليات وأنظمة الملفات، لكن Compose يضع الخدمات في شبكة واحدة افتراضياً فيصل كل Container إلى الآخر | لا، ما دامت الشبكة واحدة ومنفذ قاعدة البيانات منشوراً، وهذا ما سنراه في التجربة                                                                                                  |
| «Containers على نفس السيرفر في شبكات Docker منفصلة، وقاعدة البيانات في شبكة داخلية Internal» | فصل في الشبكة يمنع ال Proxy من الوصول إلى قاعدة البيانات، مع بقاء الجميع على نفس النواة Kernel ونفس الجهاز  | يحقق فكرة المستويات على مستوى الشبكة، لكن تعريف الضوابط يذكر «منصات منفصلة»، فقد لا يقبله المدقق للأنظمة الحساسة، وسنناقش هذا في قسم الثمن                                       |
| «SPA مع API»                                                                                 | العرض في المتصفح، والمنطق في ال API                                                                         | ثلاثة أماكن للكود، لكن المستوى الأمامي المعرض للإنترنت هو ال API نفسه إذا لم يكن أمامه Proxy أو بوابة في منطقة معزولة                                                            |
| «عندنا Microservices على Kubernetes»                                                         | خدمات كثيرة، وكل Pod يصل إلى كل Pod افتراضياً                                                               | لا يكفي، حتى تفصل المناطق بسياسات شبكة Network Policies تعمل فعلاً                                                                                                               |
| «ثلاثة VMs في ثلاث شبكات فرعية Subnets وبينها جدار ناري لا يسمح إلا للمستوى المجاور»         | فصل فعلي في الأجهزة والشبكة                                                                                 | نعم، وهذا الشكل الذي ترسمه Microsoft في مثالها: كل مستوى في Subnet، وقاعدة البيانات لا تقبل إلا من مستوى منطق العمل                                                              |

## ما الذي تطلبه الضوابط بالنص؟

في الإصدار الثاني من الضوابط الأساسية للأمن السيبراني [ECC-2:2024](https://nca.gov.sa/ar/regulatory-documents/controls-list/ecc/?ref=arabroot.io)، المكون الفرعي 2-15 «حماية تطبيقات الويب Web Application Security» هدفه حماية تطبيقات الويب الخارجية للجهة، والضابط 2-15-3 يقول إن متطلبات حمايتها يجب أن تغطي بحد أدنى عدة بنود، منها البند 2-15-3-2 ونصه في [النسخة العربية](https://cdn.nca.gov.sa/api/files/public/upload/29a9e86a-595f-4af9-8db5-88715a458a14%5FECC-2-2024---NCA.pdf?ref=arabroot.io) (صفحة 21): «استخدام مبدأ المعمارية متعددة المستويات (Multi-tier Architecture)»، وفي [النسخة الإنجليزية](https://cdn.nca.gov.sa/api/files/public/upload/86e09090-44e4-481f-bc28-355673607654%5FECC--2024-EN.pdf?ref=arabroot.io) (صفحة 28): «Adoption of the multi-tier architecture principle». ويأتي قبله البند 2-15-3-1 الذي يطلب جدار حماية تطبيقات الويب، وبعده 2-15-3-3 الذي يطلب بروتوكولات آمنة مثل HTTPS.

والضابط لا يذكر عدداً للمستويات، لكن قائمة المصطلحات في نفس الوثيقة (صفحة 26 في العربية وصفحة 37 في الإنجليزية) تعرف المعمارية متعددة المستويات بأنها «معمارية أو بنية تطبق أسلوب عميل-خادم الذي يتم فيه تطوير وصيانة منطق العملية الوظيفية، والوصول إلى البيانات، وتخزين البيانات وواجهة المستخدم كوحدات مستقلة على منصات منفصلة». ولاحظ عبارة «على منصات منفصلة» separate platforms، فالتعريف لا يكتفي بأن يكون الكود مقسماً إلى وحدات، وإنما يطلب أن تعمل هذه الوحدات على منصات منفصلة، وهذا هو المستوى Tier وليس الطبقة Layer.

أما [ضوابط الأمن السيبراني للأنظمة الحساسة](https://nca.gov.sa/ar/regulatory-documents/controls-list/cscc/?ref=arabroot.io) CSCC-1:2019 فتضيف الرقم صراحة في موضعين (صفحة 27 من [الوثيقة](https://cdn.nca.gov.sa/api/public/cms/files/f15af01c-dc59-4281-95e2-03a770655937%5FCritical-Systems-Cybersecurity-Controls.pdf?ref=arabroot.io)):

- الضابط 2-12-2 في مكون حماية تطبيقات الويب الخارجية للأنظمة الحساسة: بالإشارة إلى البند 2-15-3-2 في ECC، يجب استخدام مبدأ المعمارية متعددة المستويات بحد أدنى ثلاثة مستويات.
- البند 2-13-3-1 في مكون أمن التطبيقات الداخلية للأنظمة الحساسة: استخدام مبدأ المعمارية متعددة المستويات على ألا يقل عدد المستويات عن ثلاثة.

فالخلاصة أن كل تطبيق ويب خارجي في جهة تنطبق عليها ECC يحتاج مستويات منفصلة، وكل نظام حساس، خارجياً كان أو داخلياً، يحتاج ثلاثة مستويات على الأقل. وفي دليلنا المقترح كتبنا الضابط [ARC-ASC-02-03 المعمارية متعددة المستويات](https://arabroot.io/articles/%D8%B6%D9%88%D8%A7%D8%A8%D8%B7-%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AF%D9%84%D9%8A%D9%84-%D9%85%D9%82%D8%AA%D8%B1%D8%AD-%D9%85%D8%B9-%D8%B1%D8%A8%D8%B7-ecc-%D9%88-asvs/#d02) بهذا المعنى: أن يفصل التطبيق الخارجي إلى مستويات منفصلة على الأقل للعرض والمنطق والبيانات، بحيث لا تكون قاعدة البيانات قابلة للوصول من الإنترنت ولا من طبقة العرض مباشرة، وألا يقل عدد المستويات عن ثلاثة في الأنظمة الحساسة، وأن يكون الفصل بالشبكة وقواعد الجدار الناري وليس بالمجلدات، مع دليل الالتزام: مخطط المعمارية وقواعد الجدار الناري بين المستويات. ويكمله الضابط [ARC-ASC-12-01 جدار حماية تطبيقات الويب](https://arabroot.io/articles/%D8%B6%D9%88%D8%A7%D8%A8%D8%B7-%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AF%D9%84%D9%8A%D9%84-%D9%85%D9%82%D8%AA%D8%B1%D8%AD-%D9%85%D8%B9-%D8%B1%D8%A8%D8%B7-ecc-%D9%88-asvs/#d12) المقابل للبند 2-15-3-1، وبقية ضوابط النطاق 12 الخاصة بتشغيل التطبيق خلف ال Reverse Proxy.

## كيف تبدو المستويات في الشبكة؟

الترجمة العملية للمستويات هي مناطق شبكة Network Zones بينها جدار ناري، وكل منطقة لا تكلم إلا جارتها. والمنطقة الأمامية تسمى المنطقة منزوعة السلاح DMZ، ويعرفها [معجم NIST](https://csrc.nist.gov/glossary/term/demilitarized%5Fzone?ref=arabroot.io) بأنها شبكة محيطية Perimeter Network أو شبكة فرعية محمية Screened Subnet تفصل الشبكة الداخلية الأكثر ثقة عن الشبكة الخارجية الأقل ثقة. وكل منطقة هنا شبكة فرعية Subnet لها نطاق عناوين، وشرحنا حسابها وتخطيطها في [الشبكات الفرعية Subnets وCIDR](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-subnets-%D9%88-cidr-%D9%88-vlsm/)، وكيف تبني المناطق نفسها بالشبكات الافتراضية VLAN وVPC وأدوات مفتوحة المصدر في [تقسيم الشبكة Network Segmentation](https://arabroot.io/articles/%D8%AA%D9%82%D8%B3%D9%8A%D9%85-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-network-segmentation-vlan-%D9%88-vpc/)، والصورة التالية تبين الشكل الكامل:

![أربع مناطق من اليمين إلى اليسار: INTERNET فيها المستخدمون وجهاز أحمر للمخترقين، ثم DMZ فيها درع WAF وReverse Proxy على 10.0.1.0/24، ثم APP ZONE فيها سيرفرا تطبيق على 10.0.2.0/24، ثم DATA ZONE فيها قاعدة البيانات على 10.0.3.0/24. بين كل منطقتين جدار ناري، والأسهم الخضراء تمر على المنافذ 443 ثم 8000 ثم 5432، وثلاثة أسهم حمراء متقطعة عليها علامات منع: من الإنترنت إلى قاعدة البيانات، ومن الإنترنت إلى سيرفرات التطبيق، ومن ال DMZ إلى قاعدة البيانات. وفي الأسفل قائمة القواعد: allow any إلى DMZ على tcp/443، allow DMZ إلى APP على tcp/8000، allow APP إلى DATA على tcp/5432، deny everything else + log](https://arabroot.io/content/images/2026/10/tiers-layers-06-network-zones.webp)

ثلاث قواعد مسموحة فقط، وكل ما عداها مرفوض ومسجل

في الشكل أعلاه لاحظ التالي:

- **مستوى العرض في ال DMZ**: فيه ال Reverse Proxy وجدار حماية تطبيقات الويب WAF، وهو الوحيد الذي يقبل اتصالاً من الإنترنت، وعلى المنفذ 443 فقط، ولا يحمل أي بيانات أو أسرار لقاعدة البيانات. وقد شرحنا طبقات الحماية التي توضع فيه في [حماية تطبيقات الويب على مستوى HTTP](https://arabroot.io/articles/%D8%AD%D9%85%D8%A7%D9%8A%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D9%84%D9%89-%D9%85%D8%B3%D8%AA%D9%88%D9%89-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/).
- **مستوى التطبيق في منطقة داخلية**: يقبل الاتصال من ال DMZ فقط وعلى منفذ التطبيق فقط، وهو الوحيد الذي يملك بيانات الدخول إلى قاعدة البيانات.
- **مستوى البيانات في منطقة مقيدة**: لا يوجد أي مسار إليه من الإنترنت، ولا من ال DMZ، ويقبل الاتصال على منفذ قاعدة البيانات من مستوى التطبيق فقط، ويفضل أن تقيد قاعدة البيانات نفسها مصادر الاتصال أيضاً، كملف `pg_hba.conf` في PostgreSQL، حتى لا يعتمد الفصل على الجدار الناري وحده.
- **بيانات دخول منفصلة لكل مستوى**: حساب التطبيق في قاعدة البيانات ليس حساب المدير، ولا يملك صلاحية إنشاء الجداول أو المستخدمين في الإنتاج Production، وال Proxy لا يعرف أي كلمة مرور لقاعدة البيانات.
- **الخروج Egress مقيد أيضاً**: مستوى البيانات لا يحتاج الإنترنت، فلا تسمح له بالخروج إليه، والسبب أن المخترق الذي يصل إلى قاعدة البيانات يحتاج طريقاً لإخراج البيانات أو لتنزيل أدواته.
- **الإدارة من مسار منفصل**: دخول SSH ولوحات الإدارة لا يمر من ال DMZ، وإنما عبر VPN أو سيرفر قفز Bastion، كما شرحنا في [ما هو VPN؟](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%88-vpn-%D8%B4%D8%B1%D8%AD-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).

وفي [خارطة CTO لشركة SaaS ناشئة](https://arabroot.io/articles/%D8%AE%D8%A7%D8%B1%D8%B7%D8%A9-cto-%D9%84%D8%B4%D8%B1%D9%83%D8%A9-saas-%D9%86%D8%A7%D8%B4%D8%A6%D8%A9-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%81%D8%B1%D9%82/#network) تجد كيف تكبر هذه المناطق مع الشركة من سيرفر واحد إلى شبكات منفصلة، وإذا كان نظامك يخدم عدة عملاء على نفس البنية فالفصل بين المستويات لا يغني عن الفصل بين العملاء الذي شرحناه في [تعدد المستأجرين Multi-tenancy](https://arabroot.io/articles/%D8%AA%D8%B9%D8%AF%D8%AF-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%A7%D8%AC%D8%B1%D9%8A%D9%86-multi-tenancy-%D9%81%D9%8A-saas-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)، فالأول يحمي البيانات من الإنترنت، والثاني يحمي بيانات كل عميل من الآخر.

### كيف تثبت ذلك للمدقق؟

المدقق لا يقبل جملة «نظامنا ثلاثي المستويات» في رد مكتوب، وإنما يطلب أدلة يستطيع مراجعتها، وهذه أهمها:

1. **مخطط الشبكة Network Diagram**: يبين المناطق والشبكات الفرعية وعناوينها، وأين يقع كل سيرفر أو Container، ومن أين يدخل المستخدم ومن أين يدخل فريق الإدارة.
2. **جدول التدفقات المسموحة**: المصدر والوجهة والمنفذ وسبب كل قاعدة، مثل القائمة أسفل الصورة أعلاه، ويجب أن يطابق الإعداد الفعلي.
3. **تصدير قواعد الجدار الناري**: مخرج `nft list ruleset` أو `iptables-save` أو `ufw status verbose` على السيرفرات، أو قائمة مجموعات الأمان Security Groups وقواعدها من مزود السحابة، أو تصدير الإعداد من جدار الحماية الشبكي.
4. **إثبات من الخارج**: فحص منافذ من الإنترنت يبين أن المفتوح هو 443 فقط، ومحاولة اتصال من مستوى العرض إلى قاعدة البيانات تفشل، كما سنفعل في التجربة.
5. **إعداد قاعدة البيانات**: عنوان الاستماع، وقيود مصادر الاتصال، وصلاحيات حساب التطبيق.
6. **للبيئات المبنية على Docker**: مخرج `docker network inspect` لكل شبكة، وملف Compose الذي يبين أن قاعدة البيانات بلا منفذ منشور، كما شرحنا في [شبكات Docker وأفضل الممارسات](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/#proxy-network).

## ثمن المستويات، ومتى يكفي سيرفر واحد؟

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

لذلك فالسؤال الصحيح ليس «كم مستوى عندي؟» وإنما «ما الذي يحتاجه نظامي والضابط الذي ينطبق عليه؟»، وفيما يلي القاعدة التي نقترحها:

- **تطبيق صغير على سيرفر افتراضي VPS واحد**: يمكنك تحقيق فكرة المستويات على مستوى الشبكة: ال Reverse Proxy وحده ينشر المنفذ 443، والتطبيق في شبكة Docker داخلية لا يصل إليها إلا ال Proxy، وقاعدة البيانات في شبكة Docker داخلية أخرى لا يصل إليها إلا التطبيق ولا تنشر أي منفذ، وحساب التطبيق في قاعدة البيانات محدود الصلاحيات. وهذا يحقق الهدف الأمني الأهم: ثغرة في ال Proxy لا توصل إلى قاعدة البيانات. لكن تذكر أن المكونات كلها تشترك في نفس النواة ونفس الجهاز، فثغرة هروب من ال Container واختصاراً Container Escape أو اختراق حساب الإدارة على السيرفر يتجاوز كل هذا الفصل.
- **تطبيق ويب خارجي في جهة تنطبق عليها ECC**: التعريف يذكر «منصات منفصلة»، والقراءة الأحوط أن تكون قاعدة البيانات على الأقل على سيرفر أو VM منفصل في شبكة فرعية منفصلة، وأن يكون ال Proxy أو ال WAF في منطقة أمامية، وقبل أن تعتمد على شبكات Docker على سيرفر واحد اسأل إدارة الأمن السيبراني في جهتك واكتب القرار وسببه، والسبب أن الحكم على كفاية الفصل لها وللمدقق وليس لفريق التطوير.
- **نظام حساس Critical System**: ثلاثة مستويات على الأقل بنص CSCC، والمتوقع أن تكون على أجهزة أو VMs منفصلة في مناطق شبكة منفصلة بينها جدار ناري، مع سجلات للتدفقات المرفوضة، فلا تعتمد على سيرفر واحد مهما كانت شبكات Docker مرتبة.

وقد شرحنا في [مدخل إلى تصميم الأنظمة](https://arabroot.io/articles/%D9%85%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%89-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D8%A7%D9%84%D8%A7%D9%86%D8%B8%D9%85%D8%A9-%D9%85%D9%86-%D8%B3%D9%8A%D8%B1%D9%81%D8%B1-%D9%88%D8%A7%D8%AD%D8%AF-%D8%A7%D9%84%D9%89-%D9%85%D9%84%D8%A7%D9%8A%D9%8A%D9%86/) كيف يكبر النظام من سيرفر واحد خطوة خطوة، وفصل قاعدة البيانات هناك كان أول خطوة لأسباب الأداء، وهنا نرى أنه أول خطوة لأسباب الأمان أيضاً.

## تجربة عملية: ثلاث شبكات Docker لثلاثة مستويات

الآن سوف نبني نسخة صغيرة من الشكل السابق على جهاز واحد بـ Docker Compose: Reverse Proxy بـ nginx، وتطبيق صغير بلغة Python يقرأ من قاعدة البيانات، وقاعدة بيانات PostgreSQL، وكل منها في الشبكات التي يحتاجها فقط، ثم نتحقق من الداخل من يصل إلى من. وكل الأسماء تبدأ بـ `lab-tiers-`، والمنفذ الوحيد المنشور هو `127.0.0.1:21480`، والصورة التالية تبين ما سنبنيه والنتائج التي ظهرت:

![ثلاثة أعمدة للشبكات: lab-tiers-dmz-net من نوع bridge، وlab-tiers-app-net وlab-tiers-data-net وكلاهما internal: true. الجهاز Host على 127.0.0.1:21480 يتصل بـ lab-tiers-proxy (nginx 1.29) الواقع بين dmz وapp، وال proxy يصل إلى lab-tiers-app على app:8000 بعلامة نجاح، وال app يصل إلى lab-tiers-db على db:5432 بعلامة نجاح. سهم أحمر من ال proxy إلى قاعدة البيانات عليه علامة منع ومكتوب nslookup db: no answer و nc: timed out، وسهم أحمر من ال app إلى example.com عليه علامة منع ومكتوب name resolution fails](https://arabroot.io/content/images/2026/10/tiers-layers-07-lab-topology.webp)

كل Container في الشبكات التي يحتاجها فقط، فال Proxy لا يرى قاعدة البيانات أصلاً

### الملفات

التطبيق ملف واحد يستقبل الطلب ويسأل قاعدة البيانات عن اسم المستخدم الذي اتصل به وعنوانها وإصدارها، والمثال التالي يبين الملف `app/app.py`:

```python
import os, socket, psycopg
from http.server import BaseHTTPRequestHandler, HTTPServer

DSN = os.environ["DATABASE_URL"]

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        with psycopg.connect(DSN, connect_timeout=3) as conn:
            row = conn.execute("select current_user, inet_server_addr(), version()").fetchone()
        body = f"app={socket.gethostname()} db_user={row[0]} db_addr={row[1]}\n{row[2].split(',')[0]}\n"
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(body.encode())

HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
```

وملف `app/Dockerfile` يثبت مكتبة psycopg وقت البناء، لأن التطبيق لن يستطيع الوصول إلى الإنترنت بعد تشغيله كما سنرى:

```dockerfile
FROM python:3.13-slim
RUN pip install --no-cache-dir "psycopg[binary]==3.2.*"
COPY app.py /app.py
CMD ["python", "/app.py"]
```

وإعداد nginx في الملف `nginx.conf` يمرر كل الطلبات إلى التطبيق باسمه في الشبكة:

```nginx
server {
    listen 80;
    location / {
        proxy_pass http://app:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```

وملف `init.sql` ينشئ حساباً خاصاً بالتطبيق بلا صلاحيات إدارية، ويشغله PostgreSQL مرة واحدة عند إنشاء قاعدة البيانات:

```sql
CREATE ROLE shop_app LOGIN PASSWORD 'app-secret' NOSUPERUSER NOCREATEDB NOCREATEROLE;
GRANT CONNECT ON DATABASE shop TO shop_app;
GRANT USAGE, CREATE ON SCHEMA public TO shop_app;
```

وأهم ملف هو `compose.yaml`:

```yaml
name: lab-tiers

services:
  proxy:
    image: nginx:1.29-alpine
    container_name: lab-tiers-proxy
    ports:
      - "127.0.0.1:21480:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    networks: [dmz, app]
    depends_on: [app]

  app:
    build: ./app
    image: lab-tiers-app:1
    container_name: lab-tiers-app
    environment:
      DATABASE_URL: postgresql://shop_app:app-secret@db:5432/shop
    networks: [app, data]
    depends_on: [db]

  db:
    image: postgres:18
    container_name: lab-tiers-db
    environment:
      POSTGRES_DB: shop
      POSTGRES_PASSWORD: admin-secret
    volumes:
      - lab-tiers-pgdata:/var/lib/postgresql
      - ./init.sql:/docker-entrypoint-initdb.d/10-app-role.sql:ro
    networks: [data]

networks:
  dmz:
    name: lab-tiers-dmz-net
  app:
    name: lab-tiers-app-net
    internal: true
  data:
    name: lab-tiers-data-net
    internal: true

volumes:
  lab-tiers-pgdata:
    name: lab-tiers-pgdata
```

في الإعداد أعلاه لاحظ التالي:

- ال Proxy هو الخدمة الوحيدة التي فيها `ports`، ومربوط بالعنوان `127.0.0.1` في التجربة، وفي السيرفر الحقيقي ينشر 80 و443 فقط.
- كل خدمة في الشبكات التي تحتاجها فقط: ال Proxy في `dmz` و`app`، والتطبيق في `app` و`data`، وقاعدة البيانات في `data` وحدها، فلا توجد شبكة مشتركة بين ال Proxy وقاعدة البيانات.
- الشبكتان `app` و`data` عليهما `internal: true`، وتقول [وثائق Compose](https://docs.docker.com/reference/compose-file/networks/?ref=arabroot.io) إن Compose يعطي الشبكات اتصالاً خارجياً افتراضياً، وإن هذا الخيار ينشئ شبكة معزولة عن الخارج، فلا التطبيق ولا قاعدة البيانات يستطيعان الخروج إلى الإنترنت من خلالها.
- كلمة مرور المدير `admin-secret` لا يعرفها التطبيق، والتطبيق يتصل بالحساب المحدود `shop_app`، وفي الإنتاج تأتي هذه القيم من مدير أسرار وليس من الملف.

### التشغيل والتحقق

الآن سوف نقوم بالتشغيل ثم نرسل طلباً من الجهاز عبر ال Proxy:

```bash
docker compose up -d --build
docker compose ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
curl -s http://127.0.0.1:21480/
```

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

```text
NAME              IMAGE               STATUS              PORTS
lab-tiers-app     lab-tiers-app:1     Up About a minute
lab-tiers-db      postgres:18         Up About a minute   5432/tcp
lab-tiers-proxy   nginx:1.29-alpine   Up About a minute   127.0.0.1:21480->80/tcp

app=64b0ecc8ec31 db_user=shop_app db_addr=192.168.144.2
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu
```

لاحظ أن عمود `PORTS` لقاعدة البيانات فيه `5432/tcp` بلا سهم، أي أن المنفذ موجود داخل ال Container وغير منشور على الجهاز، وأن الطلب مر من ال Proxy إلى التطبيق ثم إلى قاعدة البيانات بالحساب `shop_app`. والآن نتأكد من الشبكات ومن فيها:

```bash
for n in lab-tiers-dmz-net lab-tiers-app-net lab-tiers-data-net; do
  docker network inspect -f '{{.Name}} internal={{.Internal}} -> {{range .Containers}}{{.Name}} {{end}}' $n
done
docker inspect -f '{{.Name}}: {{range $k,$v := .NetworkSettings.Networks}}{{$k}}={{$v.IPAddress}} {{end}}' lab-tiers-proxy lab-tiers-app lab-tiers-db
```

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

```text
lab-tiers-dmz-net internal=false -> lab-tiers-proxy
lab-tiers-app-net internal=true -> lab-tiers-proxy lab-tiers-app
lab-tiers-data-net internal=true -> lab-tiers-db lab-tiers-app
/lab-tiers-proxy: lab-tiers-app-net=192.168.112.3 lab-tiers-dmz-net=192.168.160.2
/lab-tiers-app: lab-tiers-app-net=192.168.112.2 lab-tiers-data-net=192.168.144.3
/lab-tiers-db: lab-tiers-data-net=192.168.144.2 
```

ال Proxy له عنوانان في شبكتي dmz وapp، والتطبيق له عنوانان في شبكتي app وdata، وقاعدة البيانات لها عنوان واحد في شبكة data. والآن نفترض أن المخترق وجد ثغرة في ال Proxy وصار ينفذ أوامر داخله، فهل يصل إلى قاعدة البيانات؟ نجرب بالاسم ثم بالعنوان مباشرة:

```bash
docker exec lab-tiers-proxy nslookup db
docker exec lab-tiers-proxy nc -zv -w 3 192.168.144.2 5432; echo "exit=$?"
```

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

```text
Server:		127.0.0.11
Address:	127.0.0.11:53

Non-authoritative answer:

*** Can't find db: No answer

nc: 192.168.144.2 (192.168.144.2:5432): Operation timed out
exit=1
```

خادم DNS الداخلي في Docker لا يعرف الاسم `db` لمن ليس في شبكته، ومحاولة الاتصال بالعنوان مباشرة تنتهي بانتهاء المهلة، لأنه لا يوجد مسار من شبكتي ال Proxy إلى شبكة البيانات. والآن نجرب نفس الاتصال من التطبيق، ثم نجرب خروج التطبيق إلى الإنترنت:

```bash
docker exec lab-tiers-app python -c "import socket;s=socket.create_connection(('db',5432),3);print('connected to',s.getpeername())"
docker exec lab-tiers-app python -c "import urllib.request;urllib.request.urlopen('https://example.com',timeout=5)"
```

والمخرج سوف يكون كما يلي (السطر الأخير من الخطأ الثاني):

```text
connected to ('192.168.144.2', 5432)
urllib.error.URLError: <urlopen error [Errno -3] Temporary failure in name resolution>
```

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

```bash
docker exec lab-tiers-db psql -U postgres -d shop -c "select rolname, rolsuper, rolcreatedb, rolcreaterole from pg_roles where rolname in ('postgres','shop_app');"
```

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

```text
 rolname  | rolsuper | rolcreatedb | rolcreaterole
----------+----------+-------------+---------------
 postgres | t        | t           | t
 shop_app | f        | f           | f
(2 rows)
```

### نكسر التصميم: الشكل الشائع بمستويين

الآن سوف نشغل الشكل الذي نراه كثيراً في ملفات Compose المنشورة على الإنترنت: التطبيق ينشر منفذه مباشرة بدون Proxy، وقاعدة البيانات تنشر منفذها «حتى نتصل بها من أداة الإدارة»، والاثنان في الشبكة الافتراضية، والتطبيق يتصل بحساب المدير. والمثال التالي يبين الملف `compose.flat.yaml`:

```yaml
name: lab-tiers-flat

services:
  app:
    image: lab-tiers-app:1
    container_name: lab-tiers-flat-app
    ports:
      - "127.0.0.1:21481:8000"
    environment:
      DATABASE_URL: postgresql://postgres:app-secret@db:5432/shop

  db:
    image: postgres:18
    container_name: lab-tiers-flat-db
    ports:
      - "127.0.0.1:21482:5432"
    environment:
      POSTGRES_DB: shop
      POSTGRES_PASSWORD: app-secret
    volumes:
      - lab-tiers-flat-pgdata:/var/lib/postgresql

volumes:
  lab-tiers-flat-pgdata:
    name: lab-tiers-flat-pgdata
```

نشغله ونرى الشبكة والمنافذ:

```bash
docker compose -f compose.flat.yaml up -d
docker compose -f compose.flat.yaml ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}'
docker network inspect -f '{{.Name}} internal={{.Internal}}: {{range .Containers}}{{.Name}} {{end}}' lab-tiers-flat_default
curl -s http://127.0.0.1:21481/
```

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

```text
NAME                 STATUS          PORTS
lab-tiers-flat-app   Up 21 seconds   127.0.0.1:21481->8000/tcp
lab-tiers-flat-db    Up 19 seconds   127.0.0.1:21482->5432/tcp
lab-tiers-flat_default internal=false: lab-tiers-flat-app lab-tiers-flat-db
app=81fcfe294443 db_user=postgres db_addr=192.168.176.3
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu
```

التطبيق يعمل والصفحة تظهر، ولهذا يبقى هذا الشكل في الإنتاج سنوات، لكن لاحظ ثلاثة أشياء: قاعدة البيانات منشورة على منفذ في الجهاز، والتطبيق يتصل بحساب `postgres` المدير، والشبكة غير داخلية. والآن نتصل بقاعدة البيانات من الجهاز نفسه بمكتبة psycopg2، كما سيفعل أي شخص يصل إلى هذا المنفذ ويعرف كلمة المرور أو يخمنها:

```bash
python - <<'EOF'
import psycopg2
c = psycopg2.connect(host="127.0.0.1", port=21482, user="postgres", password="app-secret", dbname="shop", connect_timeout=3)
cur = c.cursor(); cur.execute("select current_user, inet_server_port(), (select count(*) from pg_database)")
print(cur.fetchone())
EOF
docker exec lab-tiers-flat-app python -c "import urllib.request;print(urllib.request.urlopen('https://example.com',timeout=5).status)"
```

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

```text
('postgres', 5432, 4)
200
```

اتصلنا بقاعدة البيانات مباشرة بحساب المدير دون أن نمر على التطبيق وقواعده، وهذا هو نموذج المستويين بكل مشكلاته، والتطبيق المعرض للطلبات يستطيع الخروج إلى الإنترنت فيمكن للمخترق أن ينزل أدواته أو يرسل البيانات إلى الخارج. وفي التجربة ربطنا المنفذ بالعنوان `127.0.0.1` فقط، أما في سيرفر حقيقي فكثير من الملفات تكتب `"5432:5432"` بدون عنوان، فيصبح المنفذ مفتوحاً على كل عناوين السيرفر، وقد شرحنا في [شبكات Docker وأفضل الممارسات](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/#ports-vs-expose) لماذا لا يحميك ufw في هذه الحالة.

والإصلاح هو ما بنيناه في البداية خطوة خطوة: أن تحذف `ports` من قاعدة البيانات، وأن تضع أمام التطبيق Proxy وتحذف `ports` منه أيضاً، وأن تقسم الشبكة إلى `dmz` و`app` و`data` وتجعل الأخيرتين `internal: true`، وأن تنشئ للتطبيق حساباً محدوداً في قاعدة البيانات. وإذا احتجت الوصول إلى قاعدة البيانات من أداة إدارة، فاستخدم `docker exec` أو نفقاً عبر SSH أو VPN، ولا تنشر المنفذ.

### التنظيف

بعد انتهاء التجربة نحذف ما أنشأناه فقط بالاسم:

```bash
docker compose -f compose.flat.yaml down -v
docker compose down -v
docker image rm lab-tiers-app:1
```

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

- **اعتبار ترتيب المجلدات مستويات**: وجود مجلدات `controllers` و`services` و`repositories` طبقات جيدة، لكنها كلها في نفس العملية، والضابط يسأل عن المستويات.
- **اعتبار MVC أو SPA دليلاً على ثلاثة مستويات**: كلاهما يصف مكان الكود، والحكم على طريقة النشر والشبكة.
- **نشر منفذ قاعدة البيانات «مؤقتاً»**: المؤقت يبقى، والمنفذ المنشور يجعل قاعدة البيانات مستوى معرضاً مهما كان أمامها من مستويات.
- **شبكة واحدة لكل شيء**: Containers أو VMs منفصلة في شبكة مسطحة واحدة تعني أن أي مكون مخترق يصل إلى كل مكون آخر.
- **حساب المدير في التطبيق**: المستويات تحدد من يصل إلى قاعدة البيانات، والصلاحيات تحدد ما يستطيع فعله بعد الوصول، وتحتاج الاثنين.
- **مستوى أوسط شكلي**: Proxy يمرر كل شيء إلى قاعدة البيانات، أو API عام يقبل استعلامات SQL من العميل، يضيف جهازاً ولا يضيف حماية.
- **نسيان طرق الإدارة**: مستويات مرتبة للمستخدمين، ثم لوحة إدارة قاعدة البيانات مكشوفة للإنترنت أو SSH مفتوح على سيرفر البيانات، وهذا يتجاوز كل الفصل.
- **مخطط لا يطابق الواقع**: المدقق يقارن المخطط بقواعد الجدار الناري الفعلية، فأي قاعدة قديمة تسمح بـ `any` تهدم المخطط كله.

## هل نظامك ثلاثي الطبقات Three-tier فعلاً؟ قائمة تحقق

1. هل يمكنك أن ترسم مخططاً فيه المناطق والشبكات الفرعية ومكان كل مكون، ويطابق ما في الإنتاج؟
2. هل الشيء الوحيد الذي يقبل اتصالاً من الإنترنت هو ال Reverse Proxy أو ال WAF، وعلى 443 فقط؟
3. هل يعمل ال Proxy في منطقة أو شبكة منفصلة عن التطبيق، ولا يحمل أي أسرار لقاعدة البيانات؟
4. هل يقبل التطبيق الاتصال من مستوى ال Proxy فقط وعلى منفذه فقط؟
5. هل قاعدة البيانات بلا منفذ منشور على الإنترنت، ولا تقبل إلا من مستوى التطبيق، بقاعدة في الجدار الناري وقيد في إعداد قاعدة البيانات نفسها؟
6. هل محاولة الاتصال من مستوى العرض إلى قاعدة البيانات تفشل فعلاً عند تجربتها؟
7. هل حساب التطبيق في قاعدة البيانات محدود الصلاحيات، ومختلف عن حساب المدير؟
8. هل الخروج إلى الإنترنت من مستوى البيانات ممنوع، ومن مستوى التطبيق محدود بما يحتاجه؟
9. هل الإدارة عبر VPN أو Bastion، وليس عبر منافذ مفتوحة على أي مستوى؟
10. إذا كان النظام حساساً: هل المستويات الثلاثة على أجهزة أو VMs منفصلة في مناطق منفصلة؟
11. هل لديك تصدير حديث لقواعد الجدار الناري ونتيجة فحص منافذ من الخارج لتقدمهما للمدقق؟

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- الطبقة Layer تقسيم منطقي للكود داخل برنامج واحد، والمستوى Tier فصل فعلي في التشغيل بين أجهزة أو عمليات بينها شبكة وجدار ناري، والضوابط تطلب المستويات.
- الحاسوب المركزي كان مستوى واحداً، وClient-Server كان مستويين يحمل فيهما كل جهاز بيانات دخول قاعدة البيانات، والمعمارية ثلاثية المستويات وضعت سيرفر التطبيقات في الوسط فلا يرى قاعدة البيانات أحد غيره.
- MVC نمط ابتكر عام 1978 لترتيب الكود وواجهة المستخدم، وSPA مع API طريقة لتوزيع طبقة العرض، وكلاهما لا يحدد عدد المستويات، والحكم على طريقة النشر.
- البند 2-15-3-2 في ECC-2:2024 يطلب مبدأ المعمارية متعددة المستويات لتطبيقات الويب الخارجية، وتعريفه في الوثيقة يذكر «منصات منفصلة»، والضابط 2-12-2 والبند 2-13-3-1 في CSCC يطلبان ثلاثة مستويات على الأقل للأنظمة الحساسة.
- في الشبكة: Proxy وWAF في ال DMZ، والتطبيق في منطقة داخلية، وقاعدة البيانات في منطقة مقيدة، وكل منطقة تكلم جارتها فقط، مع حسابات منفصلة وخروج مقيد وإدارة من مسار منفصل.
- على سيرفر واحد تحقق شبكات Docker الداخلية فكرة الفصل كما رأينا في التجربة، أما الأنظمة الحساسة فتحتاج أجهزة منفصلة، والحكم على الكفاية لإدارة الأمن السيبراني والمدقق.

وأقرب المقالات إلى هذا الموضوع على الموقع:

[![](https://arabroot.io/content/images/2026/10/system-design-intro-cover-cover.webp)دليلمدخل إلى تصميم الأنظمة: من سيرفر واحد إلى ملايين المستخدمينكيف يكبر متجر إلكتروني من سيرفر واحد إلى نظام يخدم ملايين المستخدمين، وما الثمن الذي تدفعه في كل خطوة.](https://arabroot.io/articles/%D9%85%D8%AF%D8%AE%D9%84-%D8%A7%D9%84%D9%89-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D8%A7%D9%84%D8%A7%D9%86%D8%B8%D9%85%D8%A9-%D9%85%D9%86-%D8%B3%D9%8A%D8%B1%D9%81%D8%B1-%D9%88%D8%A7%D8%AD%D8%AF-%D8%A7%D9%84%D9%89-%D9%85%D9%84%D8%A7%D9%8A%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/docker-networking-cover-cover.webp)دليلشبكات Docker وأفضل الممارسات مع Docker Composeلماذا يبقى المنفذ الذي تنشره في Compose مفتوحاً للإنترنت رغم ufw، وكيف تصل التطبيقات إلى بعضها عبر شبكات Docker دون نشر منافذ.](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/http-protection-cover-cover.webp)دليلحماية تطبيقات الويب بطبقات أمان في ال Reverse Proxyكلمة المرور وحدها لا تحمي تطبيقاً مكشوفاً على الإنترنت، لذلك نضع أمامه طبقات حماية في ال Reverse Proxy نفسه.](https://arabroot.io/articles/%D8%AD%D9%85%D8%A7%D9%8A%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D9%84%D9%89-%D9%85%D8%B3%D8%AA%D9%88%D9%89-http/)[![](https://arabroot.io/content/images/2026/10/appsec-controls-cover-cover.webp)دليلضوابط أمن التطبيقات: دليل مقترح بنطاقات وضوابط مرقمة، مربوط بضوابط NCA وOWASP ASVSمقترح مستقل من عرب رووت بتسعين ضابطاً مرقماً لأمن تطبيقات الويب وواجهات API وخط CI/CD، لكل ضابط متطلب ودليل للمدقق، مربوط بالبنود التي يدعمها في ECC-2:2024 وCSCC وبأرقام OWASP ASVS 5.0، مع ملف Excel للتقييم الذاتي.](https://arabroot.io/articles/%D8%B6%D9%88%D8%A7%D8%A8%D8%B7-%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AF%D9%84%D9%8A%D9%84-%D9%85%D9%82%D8%AA%D8%B1%D8%AD-%D9%85%D8%B9-%D8%B1%D8%A8%D8%B7-ecc-%D9%88-asvs/)[![](https://arabroot.io/content/images/2026/10/nca-ecc-cover-cover.webp)دليلالضوابط الأساسية للأمن السيبراني NCA ECC بلغة بسيطةكيف تقرأ الضوابط الأساسية ECC دون أن تضيع في أرقامها، وعلى من تنطبق، وما المطلوب من جهتك فعلاً في كل مكون.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B6%D9%88%D8%A7%D8%A8%D8%B7-%D8%A7%D9%84%D8%A7%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-%D9%84%D9%84%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B3%D9%8A%D8%A8%D8%B1%D8%A7%D9%86%D9%8A-nca-ecc-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9/)[![](https://arabroot.io/content/images/2026/10/saas-cto-blueprint-cover-cover.webp)دليلخارطة CTO لشركة SaaS ناشئة من اليوم الأول إلى خمسين موظفاًماذا يجهز المدير التقني في كل مرحلة من نمو شركة SaaS ناشئة، وما الذي يؤجله حتى يحتاجه فعلاً.](https://arabroot.io/articles/%D8%AE%D8%A7%D8%B1%D8%B7%D8%A9-cto-%D9%84%D8%B4%D8%B1%D9%83%D8%A9-saas-%D9%86%D8%A7%D8%B4%D8%A6%D8%A9-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%81%D8%B1%D9%82/)

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

- أكتوبر 2026: كتابة المقال، ومراجعة نصوص ECC-2:2024 بالعربية والإنجليزية وCSCC-1:2019، وتنفيذ التجربة العملية على Docker Engine 29.8.0 وDocker Compose 5.5.1 مع nginx 1.29.8 وPostgreSQL 18.6 وPython 3.13.16 وpsycopg 3.2.13.