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

# شبكات Docker وأفضل الممارسات مع Docker Compose
- URL: https://arabroot.io/articles/شبكات-docker-وأفضل-الممارسات/
- Published: 2026-10-04T04:40:00.000Z
- Updated: 2026-10-05T17:30:46.000Z
- Description: كل منفذ تنشره في ملف Compose بدون عنوان يصبح مفتوحاً على الإنترنت حتى لو كان ufw مفعلاً. يشرح هذا الدليل أنواع شبكات Docker، والنمط الذي نستخدمه في كل أدلة الموقع: شبكة proxy مشتركة لل Reverse Proxy والتطبيقات، وشبكة داخلية لقاعدة بيانات كل مشروع.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, Docker والحاويات, الشبكات والوصول, الاستضافة الذاتية, Docker

عندما تشغل أول تطبيق على سيرفرك بـ Docker Compose فغالباً سوف تنسخ ملف `compose.yaml` من صفحة المشروع كما هو، وفي هذا الملف سطر `ports` ينشر منفذ التطبيق على السيرفر، وأحياناً سطر آخر ينشر منفذ قاعدة البيانات Database حتى تتصل بها من جهازك، ومع التطبيق الخامس يصبح على السيرفر عشرة منافذ مفتوحة لا تتذكر أنت لماذا فتحت نصفها. والمشكلة أن هذه المنافذ ليست مفتوحة على السيرفر وحده كما يظن الكثيرون، وإنما على الإنترنت، حتى لو كان جدار الحماية Firewall مفعلاً.

وفي [دليل Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/) ذكرنا أننا سوف نضع NPM وكل التطبيقات التي يخدمها على شبكة Docker مشتركة Docker Network اسمها `proxy`، وعلى هذه الشبكة يصل NPM إلى كل Container باسمه، وبالتالي لا تحتاج التطبيقات إلى نشر أي منفذ على السيرفر، ويبقى ال Reverse Proxy هو الطريق الوحيد إليها. وهذه الجملة تختصر نمطاً كاملاً نستخدمه في كل أدلة الموقع، سواءً كان ال Reverse Proxy هو NPM أو Traefik أو Caddy، لذلك سوف نشرحه هنا مرة واحدة بالتفصيل، ونبدأ من الأساس: ما هي شبكة Docker، وما أنواعها، ولماذا يعمل هذا النمط.

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

- ما الذي يحدث عندما تنشر كل منفذ على السيرفر، ولماذا يتجاوز Docker جدار `ufw`.
- أنواع شبكات Docker Network Drivers: `bridge` و`host` و`none` و`overlay` و`macvlan` و`ipvlan`، ومتى يناسبك كل منها.
- الشبكة التي ينشئها Compose لكل مشروع، وكيف تصل ال Containers إلى بعضها بالاسم.
- الفرق بين `ports` و`expose`، ومتى لا تنشر أي منفذ أصلاً.
- النمط الذي نستخدمه في الموقع: شبكة `proxy` مشتركة لل Reverse Proxy والتطبيقات، وشبكة داخلية Internal Network خاصة بكل مشروع لقاعدة بياناته.
- الأخطاء الشائعة: خطأ 502 من ال Reverse Proxy، وتعارض الأسماء بين المشاريع، والمخرج الافتراضي Default Gateway عند الاتصال بشبكتين، ونفاد نطاقات العناوين Address Pools، وملاحظات IPv6.

وإذا لم يكن Docker مثبتاً على سيرفرك بعد، فابدأ بدليل [تثبيت Docker Engine وDocker Compose على Ubuntu](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-%D8%B9%D9%84%D9%89-ubuntu/).

## ماذا يحدث عندما تنشر كل منفذ؟

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

![مخطط يقارن بين سيرفر تنشر فيه كل التطبيقات منافذها فيصل إليها الإنترنت رغم جدار الحماية، وسيرفر لا يفتح إلا منفذي ال Reverse Proxy وتصل التطبيقات إليه عبر شبكة Docker داخلية](https://arabroot.io/content/images/2026/10/docker-networking-01-exposed-ports.webp)

الطريقة الشائعة مقابل أفضل ممارسة: منفذان فقط يراهما الإنترنت

```yaml
services:
  app:
    image: traefik/whoami:v1.12.0
    ports:
      - "8080:80"
  db:
    image: postgres:18.0-alpine
    environment:
      POSTGRES_PASSWORD: change-me
    ports:
      - "5432:5432"
```

هذا الملف يعمل، والتطبيق يظهر على `http://203.0.113.10:8080`، ولكن لاحظ ما فعلته دون أن تقصد:

- الصيغة `"8080:80"` لا تحدد عنواناً على السيرفر، لذلك [ينشر Docker المنفذ على كل عناوين السيرفر](https://docs.docker.com/engine/network/port-publishing/?ref=arabroot.io#setting-the-default-bind-address-for-containers)، أي `0.0.0.0` و`[::]`، وبالتالي يصل إليه كل من يعرف عنوان السيرفر العام Public IP.
- قاعدة البيانات أصبحت كذلك على الإنترنت بالمنفذ 5432، ولا يحميها إلا الباسورد، والتطبيق نفسه لم يكن يحتاج هذا المنفذ أصلاً، فهو يصل إلى قاعدة البيانات من داخل شبكة Docker كما سيأتي.
- كل تطبيق جديد يحتاج منفذاً جديداً، ورقماً يتذكره المستخدم، وشهادة TLS Certificate خاصة به.

وقد يتساءل البعض: ولكني فعلت جدار `ufw` ولم أسمح إلا بالمنافذ 22 و80 و443، فكيف يصل أحد إلى 5432؟ والإجابة أن [توثيق Docker نفسه](https://docs.docker.com/engine/network/packet-filtering-firewalls/?ref=arabroot.io#docker-and-ufw) يقول إن Docker و`ufw` يستخدمان قواعد جدار الحماية بطريقة لا تتوافق، فعندما تنشر منفذاً يحول Docker الحركة Traffic المتجهة إلى ال Container قبل أن تمر على قواعد `ufw`، وبالتالي يبقى المنفذ مفتوحاً مهما كتبت في `ufw`. وهذه أشهر مفاجأة يواجهها من ينقل خدماته إلى Docker، وقد شرحناها في [دليل تأمين خادم VPS](https://arabroot.io/articles/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-vps-%D9%85%D9%86-%D8%A3%D9%88%D9%84-%D8%AF%D8%AE%D9%88%D9%84/) وفي دليل التثبيت.

والصورة التالية تبين الطريق الذي تسلكه الحزمة إلى المنفذ المنشور، ولماذا لا تمر على قواعد `ufw`:

![مخطط يوضح حزمة تصل إلى المنفذ 5432 فيغير Docker وجهتها إلى عنوان ال Container، فتمر في سلسلة FORWARD التي تبدأ بقواعد Docker ولا تمر على قواعد ufw في سلسلة INPUT](https://arabroot.io/content/images/2026/10/docker-networking-02-ufw-bypass.webp)

Docker يغير وجهة الحزمة إلى ال Container قبل قواعد ufw، فلا يراها جدار الحماية

📌

في 6 يناير 2017 نشرت MongoDB على مدونتها تحذيراً بعنوان [How to Avoid a Malicious Attack That Ransoms Your Data](https://web.archive.org/web/20171218225950/https://www.mongodb.com/blog/post/how-to-avoid-a-malicious-attack-that-ransoms-your-data)، بعد موجة هجمات على قواعد MongoDB التي كان أصحابها يشغلونها مفتوحة على الإنترنت دون حماية، حيث كان المهاجم يحذف قاعدة البيانات ويترك مكانها طلب فدية Ransom مقابل إعادتها. وذكرت الشركة في نفس التحذير أن أشهر طرق تثبيتها تقصر الاتصال على localhost افتراضياً، ونصحت بنفس الإعداد مع أي طريقة تثبيت أخرى. والدرس لمن يستخدم Docker أن سطر `ports` واحداً بدون عنوان يكفي ليضع قاعدة بياناتك على الإنترنت.

والحل الأول الذي يخطر على البال هو أن تنشر كل شيء على `127.0.0.1`، وهذا أفضل بكثير، ولكنه لا يحل المشكلة كاملة، فال Reverse Proxy الذي يعمل في Container لا يصل إلى `127.0.0.1` الخاص بالسيرفر بسهولة، لأن `127.0.0.1` داخل كل Container يشير إلى ال Container نفسه، وبالتالي تحتاج إلى حيل مثل `host.docker.internal` أو تشغيل ال Reverse Proxy بشبكة `host`. والحل الأنسب هو ألا تنشر منافذ التطبيقات أصلاً، وأن تضعها مع ال Reverse Proxy على شبكة Docker واحدة، وحتى نفهم لماذا يعمل هذا الحل نحتاج أن نفهم شبكات Docker أولاً.

## ما هي شبكة Docker؟

شبكة Docker هي شبكة افتراضية Virtual Network ينشئها Docker على السيرفر، ويتصل بها ال Container كما يتصل جهاز بمفتاح شبكة Switch بكابل، حيث يأخذ عنوان IP منها ويستطيع أن يكلم كل من يتصل بنفس الشبكة. وكما يتصل الجهاز الواحد بأكثر من شبكة عبر أكثر من كابل، [يتصل ال Container بأكثر من شبكة](https://docs.docker.com/engine/network/?ref=arabroot.io#connecting-to-multiple-networks)، فيكون له عنوان في كل واحدة منها.

والأهم من العنوان هو الاسم، ففي الشبكات التي تنشئها أنت (وليس الشبكة الافتراضية كما سيأتي) يعمل داخل Docker سيرفر DNS مدمج Embedded DNS Server على العنوان `127.0.0.11`، وهو [يحول اسم ال Container أو اسم الخدمة Service إلى عنوانها](https://docs.docker.com/engine/network/?ref=arabroot.io#dns-services) على هذه الشبكة، أما الأسماء الأخرى مثل `example.com` فيمررها إلى سيرفرات DNS المضبوطة على السيرفر. وبالتالي لا تحتاج أن تعرف عنوان قاعدة البيانات ولا أن تحفظه في إعدادات التطبيق، فيكفي أن تكتب `db`، وإذا أعاد Docker إنشاء ال Container بعنوان جديد فالاسم يبقى كما هو.

وعند تثبيت Docker سوف تجد ثلاث شبكات جاهزة، وتعرضها بالأمر التالي:

```bash
docker network ls --filter type=builtin
```

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

```bash
NETWORK ID     NAME      DRIVER    SCOPE
7e53e218a864   bridge    bridge    local
c9369ab16874   host      host      local
e09463bc286f   none      null      local
```

ولاحظ أن عمود DRIVER هو نوع الشبكة، والاسم شيء آخر، فالشبكة التي اسمها `bridge` هي الشبكة الافتراضية Default Bridge، ونوعها `bridge`، وكل شبكة تنشئها أنت بالأمر `docker network create` نوعها `bridge` أيضاً ما لم تحدد غيره.

## أنواع الشبكات Network Drivers

يوفر Docker [عدة أنواع من الشبكات](https://docs.docker.com/engine/network/drivers/?ref=arabroot.io)، ولكل نوع مشغل Driver يحدد كيف يتصل ال Container بالسيرفر وبغيره، وفيما يلي كل نوع ومتى يناسبك.

والصورة التالية تبين أين يقف ال Container من السيرفر والشبكة في كل نوع منها:

![مخطط يوضح أنواع شبكات Docker: bridge شبكة خاصة على السيرفر تخرج بترجمة العناوين، وhost شبكة السيرفر نفسه، وnone بدون شبكة، وoverlay شبكة واحدة على عدة سيرفرات، وmacvlan وipvlan عنوان من شبكة المنزل](https://arabroot.io/content/images/2026/10/docker-networking-04-drivers.webp)

أنواع شبكات Docker وأين يتصل ال Container في كل منها

### شبكة bridge: الافتراضية والتي تنشئها أنت

شبكة [bridge](https://docs.docker.com/engine/network/drivers/bridge/?ref=arabroot.io) هي النوع الافتراضي، وهي شبكة خاصة على نفس السيرفر، تتصل بها ال Containers فتكلم بعضها، وتخرج إلى الإنترنت عبر السيرفر بترجمة العناوين NAT، ولا يصل إليها أحد من الخارج إلا عبر منفذ تنشره. ولكن هناك فرق كبير بين الشبكة الافتراضية التي اسمها `bridge`، وبين شبكة bridge تنشئها أنت User-defined Bridge، والفرق يظهر بالمثال التالي.

سوف نشغل Container اسمه `db` بدون تحديد أي شبكة، فيتصل تلقائياً بالشبكة الافتراضية، ثم نحاول الوصول إليه باسمه من Container آخر على نفس الشبكة:

```bash
docker run -d --name db alpine:3.24 sleep 600
docker run --rm alpine:3.24 ping -c 1 db
```

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

```bash
ping: bad address 'db'
```

الآن سوف نقوم بإنشاء شبكة خاصة بنا اسمها `demo`، ونوصل بها نفس ال Container وهو يعمل، ثم نكرر نفس المحاولة من هذه الشبكة:

```bash
docker network create demo
docker network connect demo db
docker run --rm --network demo alpine:3.24 ping -c 1 db
```

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

```bash
PING db (172.31.0.2): 56 data bytes
64 bytes from 172.31.0.2: seq=0 ttl=64 time=0.309 ms

--- db ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.309/0.309/0.309 ms
```

وهذا هو الفرق الأول، والسبب أن [الشبكة الافتراضية لا تحول الأسماء إلى عناوين](https://docs.docker.com/engine/network/drivers/bridge/?ref=arabroot.io#differences-between-user-defined-bridges-and-the-default-bridge)، فال Containers عليها تتصل ببعضها بالعنوان فقط، إلا إذا استخدمت الخيار `--link` الذي يصفه التوثيق بأنه قديم Legacy. وبقية الفروق كما يلي:

- العزل Isolation: كل Container تشغله بدون تحديد شبكة يتصل بالشبكة الافتراضية، وبالتالي تستطيع تطبيقات لا علاقة لها ببعضها أن تتصل ببعضها، أما الشبكة التي تنشئها فلا يتصل بها إلا من تضيفه أنت.
- التوصيل أثناء التشغيل: تستطيع أن توصل Container بشبكة تنشئها أو تفصله عنها وهو يعمل، كما فعلنا بالأمر `docker network connect` أعلاه، أما فصله عن الشبكة الافتراضية فيحتاج إلى إيقافه وإعادة إنشائه.
- الإعدادات: كل شبكة تنشئها لها إعداداتها الخاصة، أما الشبكة الافتراضية فإعداداتها واحدة لكل ال Containers المتصلة بها.

ولذلك يقول التوثيق صراحة إن الشبكة الافتراضية `bridge` تفصيل قديم في Docker و**لا ينصح باستخدامها في الإنتاج Production**. والخبر الجيد أنك إذا كنت تستخدم Compose فأنت لا تستخدمها أصلاً، لأن Compose ينشئ شبكة خاصة لكل مشروع كما سيأتي. احذف ما أنشأناه في هذا المثال:

```bash
docker rm -f db
docker network rm demo
```

والصورة التالية تلخص الفرق بين الشبكتين بالمثال الذي نفذناه:

![مخطط يقارن بين الشبكة الافتراضية bridge التي يفشل فيها ping db، وشبكة demo التي يجيب فيها سيرفر DNS المدمج 127.0.0.11 بعنوان قاعدة البيانات فينجح الاتصال بالاسم](https://arabroot.io/content/images/2026/10/docker-networking-03-bridge-dns.webp)

على الشبكة التي تنشئها يحول DNS المدمج اسم ال Container إلى عنوانه

### شبكة host وشبكة none

شبكة [host](https://docs.docker.com/engine/network/drivers/host/?ref=arabroot.io) تلغي العزل بين ال Container والسيرفر، فيستخدم ال Container شبكة السيرفر مباشرة، والتطبيق الذي يستمع على المنفذ 80 داخله يستمع على المنفذ 80 في السيرفر نفسه. لذلك لا معنى لنشر المنافذ معها، وإذا كتبت `-p` أو `ports` فسوف يتجاهلها Docker ويعرض التحذير `Published ports are discarded when using host network mode`. وهي تعمل على Docker Engine في Linux، وعلى Docker Desktop من الإصدار 4.34 بعد تفعيلها من الإعدادات. ويحتاجها القليل من التطبيقات، مثل التي تكتشف الأجهزة على شبكتك المنزلية بالبث Broadcast أو التي تتعامل مع عدد كبير جداً من المنافذ، أما باقي التطبيقات فلا تستخدمها معها، والسبب أن ال Container يصبح قادراً على الاستماع على أي منفذ في السيرفر دون أن يظهر ذلك في ملف Compose.

أما شبكة [none](https://docs.docker.com/engine/network/drivers/none/?ref=arabroot.io) فهي العكس تماماً، حيث [تعزل ال Container عزلاً كاملاً](https://docs.docker.com/engine/network/drivers/?ref=arabroot.io) عن السيرفر وعن غيره، فلا يبقى له إلا واجهة loopback، وهي مناسبة لمهمة تعالج ملفات في Volume ولا تحتاج أي اتصال، كأداة تحويل أو ضغط تشغلها مرة واحدة.

### شبكة overlay: لأكثر من سيرفر

كل ما سبق يعمل على سيرفر واحد، أما شبكة [overlay](https://docs.docker.com/engine/network/drivers/overlay/?ref=arabroot.io) فتربط عدة سيرفرات عليها Docker في شبكة واحدة، فيكلم ال Container على السيرفر الأول Container على السيرفر الثاني باسمه. وهي تعمل مع Docker Swarm، وهو أداة Docker لإدارة مجموعة سيرفرات Cluster، ولن تحتاجها ما دامت كل خدماتك على سيرفر واحد، وهذا حال أغلب من يستضيف تطبيقاته بنفسه.

### شبكة macvlan وشبكة ipvlan: عندما تريد عنواناً من شبكتك المنزلية

في المختبر المنزلي Homelab قد تريد أن يظهر Container على شبكة المنزل كأنه جهاز مستقل، بعنوان مثل `192.168.1.50` يراه ال Router وكل الأجهزة، كما يفعل البعض مع Pi-hole أو Home Assistant. وهذا ما تفعله شبكة [macvlan](https://docs.docker.com/engine/network/drivers/macvlan/?ref=arabroot.io)، حيث تعطي ال Container عنوان MAC Address خاصاً به، فيبدو على الشبكة كجهاز حقيقي، أما شبكة [ipvlan](https://docs.docker.com/engine/network/drivers/ipvlan/?ref=arabroot.io) فتعطيه عنوان IP خاصاً به مع نفس عنوان MAC الخاص بالسيرفر، وهي الخيار عندما يرفض ال Switch أو نقطة الوصول اللاسلكية Access Point أكثر من عنوان MAC على نفس المنفذ.

ولكن لاحظ أمرين قبل أن تختار macvlan: الأول أن ال Container على هذه الشبكة **لا يستطيع أن يتصل بالسيرفر نفسه مباشرة**، وهذا قيد في نواة Linux Kernel وليس في Docker، والثاني أنك تحتاج إلى حجز نطاق من العناوين لا يوزعه سيرفر DHCP في ال Router حتى لا يتكرر العنوان. لذلك إذا كان التطبيق يعمل خلف Reverse Proxy فشبكة bridge أنسب له، واترك macvlan للتطبيقات التي تحتاج فعلاً أن تكون جهازاً على شبكتك.

### المقارنة بين الأنواع

| النوع                                      | ماذا يفعل                                                                   | متى يناسبك                                                                           |
| ------------------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| الشبكة الافتراضية Default Bridge           | شبكة bridge واحدة لكل Container لا تحدد له شبكة، والاتصال فيها بالعنوان فقط | للتجربة السريعة بالأمر docker run، ولا ينصح بها في الإنتاج                           |
| شبكة bridge تنشئها أنت User-defined Bridge | شبكة خاصة على نفس السيرفر مع DNS بالأسماء وعزل عن غيرها                     | الخيار الطبيعي لكل تطبيق على سيرفر واحد، وهي ما ينشئه Compose                        |
| شبكة السيرفر Host                          | ال Container يستخدم شبكة السيرفر مباشرة دون عزل                             | تطبيقات قليلة تحتاج البث على الشبكة المحلية أو عدداً كبيراً من المنافذ               |
| بدون شبكة None                             | عزل كامل، لا يوجد إلا loopback                                              | مهام تعالج ملفات ولا تحتاج أي اتصال                                                  |
| Overlay                                    | شبكة واحدة تمتد على عدة سيرفرات                                             | Docker Swarm وال Clusters                                                            |
| Macvlan                                    | عنوان MAC وعنوان IP من شبكتك الفعلية لكل Container                          | جهاز يجب أن يظهر على شبكة المنزل أو المكتب بعنوانه، مع قيد عدم الاتصال بالسيرفر نفسه |
| IPvlan                                     | عنوان IP من شبكتك الفعلية مع عنوان MAC الخاص بالسيرفر                       | نفس حالة macvlan عندما يقيد ال Switch عدد عناوين MAC                                 |

## كيف يربط Compose خدمات المشروع ببعضها؟

عندما تنفذ `docker compose up` [ينشئ Compose شبكة bridge اسمها <اسم المشروع>\_default](https://docs.docker.com/compose/how-tos/networking/?ref=arabroot.io) ويوصل بها كل خدمات الملف، واسم المشروع Project Name هو اسم المجلد الذي فيه الملف ما لم تحدد غيره بالمفتاح `name`. لنأخذ مثالاً، وهو ملف المثال الأول بعد أن نحذف منه منفذ قاعدة البيانات، ونقصر منفذ التطبيق على السيرفر نفسه، وذلك في المجلد `/opt/shop`:

```yaml
services:
  app:
    image: traefik/whoami:v1.12.0
    ports:
      - "127.0.0.1:8080:80"
  db:
    image: postgres:18.0-alpine
    environment:
      POSTGRES_PASSWORD: change-me
```

```bash
cd /opt/shop
docker compose up -d
docker network ls --filter name=shop
docker compose ps --format 'table {{.Name}}\t{{.Service}}\t{{.Ports}}'
```

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

```bash
NETWORK ID     NAME           DRIVER    SCOPE
ca89b52d3da5   shop_default   bridge    local
NAME         SERVICE   PORTS
shop-app-1   app       127.0.0.1:8080->80/tcp
shop-db-1    db        5432/tcp
```

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

- الشبكة اسمها `shop_default`، وأسماء ال Containers تبدأ باسم المشروع أيضاً، وهكذا يميز Docker مشروعاً عن آخر على نفس السيرفر.
- المنفذ في سطر `app` مكتوب بالصيغة `127.0.0.1:8080->80/tcp`، أي منشور على السيرفر نفسه فقط.
- أما `5432/tcp` في سطر `db` فليس منشوراً على السيرفر، وإنما هو المنفذ الذي تعلن عنه ال Image، ولا يصل إليه إلا من يتصل بنفس الشبكة، وغياب السهم `->` هو ما يدلك على ذلك.

الآن سوف نتأكد أن الخدمتين تتصلان ببعضهما بالاسم، وذلك من Container مؤقت نوصله بشبكة المشروع:

```bash
docker run --rm --network shop_default alpine:3.24 sh -c 'ping -c 1 db; ping -c 1 app' | grep 'bytes from'
```

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

```bash
64 bytes from 172.31.0.2: seq=0 ttl=64 time=2.611 ms
64 bytes from 172.31.0.3: seq=0 ttl=64 time=0.416 ms
```

وبالتالي يكفي أن تكتب في إعداد التطبيق أن قاعدة البيانات هي `db` على المنفذ 5432، وهذا ما [يشرحه توثيق Compose](https://docs.docker.com/compose/how-tos/networking/?ref=arabroot.io): الاتصال بين الخدمات يستخدم منفذ ال Container، أما المنفذ المنشور على السيرفر فهو للوصول من خارج الشبكة فقط. ولهذا لم يكن هناك أي داع لنشر المنفذ 5432 في المثال الأول.

### الشبكات التي تعرفها بنفسك

الشبكة الافتراضية للمشروع تكفي إذا كانت كل الخدمات يجب أن تكلم بعضها، أما إذا أردت أن تفصل بينها فعرف شبكاتك في المفتاح [networks](https://docs.docker.com/reference/compose-file/networks/?ref=arabroot.io) أعلى الملف، ثم حدد لكل خدمة الشبكات التي تتصل بها. وهنا ثلاثة إعدادات سوف نستخدمها في النمط الذي نشرحه بعد قليل:

- الإعداد [external: true](https://docs.docker.com/reference/compose-file/networks/?ref=arabroot.io#external): الشبكة موجودة مسبقاً ولا يديرها هذا المشروع، فلا ينشئها Compose ولا يحذفها مع `docker compose down`، ويتوقف بخطأ إذا لم يجدها.
- الإعداد [internal: true](https://docs.docker.com/reference/compose-file/networks/?ref=arabroot.io#internal): شبكة معزولة عن الخارج، فال Containers عليها تكلم بعضها فقط، ولا تخرج إلى الإنترنت.
- الإعداد [name](https://docs.docker.com/reference/compose-file/networks/?ref=arabroot.io#name): يعطي الشبكة اسماً ثابتاً لا يضيف إليه Compose اسم المشروع.

### أسماء إضافية للخدمة Aliases

اسم الخدمة هو اسمها على الشبكة، ولكن قد تحتاج اسماً آخر، كأن يبحث التطبيق عن قاعدة البيانات باسم `postgres` واسم الخدمة عندك `db`، أو تريد اسماً لا يتكرر على شبكة مشتركة كما سيأتي في قسم المشكلات. وهنا يأتي دور [aliases](https://docs.docker.com/reference/compose-file/services/?ref=arabroot.io#aliases)، وهي أسماء إضافية للخدمة على شبكة محددة، ولاحظ أن الاسم الإضافي يعمل على الشبكة التي عرفته فيها فقط:

```yaml
services:
  web:
    image: traefik/whoami:v1.12.0
    networks:
      proxy:
        aliases:
          - blog-web

networks:
  proxy:
    external: true
```

### كيف ترى الشبكات وما عليها؟

هذه الأوامر سوف تحتاجها كثيراً عند حل المشكلات:

- [docker network ls](https://docs.docker.com/reference/cli/docker/network/ls/?ref=arabroot.io): قائمة الشبكات ونوع كل منها.
- [docker network inspect proxy](https://docs.docker.com/reference/cli/docker/network/inspect/?ref=arabroot.io): تفاصيل الشبكة، ومنها النطاق Subnet وال Containers المتصلة بها وعنوان كل منها.
- `docker network connect proxy <container>` و`docker network disconnect proxy <container>`: توصيل Container بشبكة أو فصله عنها وهو يعمل. ولكن تذكر أن هذا التغيير يضيع عندما يعيد Compose إنشاء ال Container، لذلك استخدمه للتجربة، أما الإعداد الدائم فمكانه ملف Compose.

ومخرج `docker network inspect` طويل، لذلك نفضل أن نطلب منه ال Containers وعناوينها فقط بالصيغة التالية:

```bash
docker network inspect proxy --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
```

## متى تنشر منفذاً ومتى لا تنشره؟

في ملف Compose ثلاث حالات للمنفذ، والفرق بينها هو الفرق بين سيرفر آمن وسيرفر مكشوف:

- النشر على كل العناوين `ports: ["8080:80"]`: المنفذ مفتوح للإنترنت، ويتجاوز `ufw` كما ذكرنا، و[توثيق Compose](https://docs.docker.com/reference/compose-file/services/?ref=arabroot.io#ports) يحذر من ذلك صراحة. ولا تستخدم هذه الصيغة إلا مع المنفذين 80 و443 الخاصين بال Reverse Proxy.
- النشر على السيرفر وحده `ports: ["127.0.0.1:8080:80"]`: المنفذ متاح من السيرفر نفسه فقط، وتصل إليه من جهازك عبر نفق SSH Tunnel. وهذا مناسب لواجهات الإدارة Admin UIs التي لا تريدها على الإنترنت أبداً، ولاحظ أن [الإصدارات الأقدم من Docker 28.0](https://docs.docker.com/engine/network/port-publishing/?ref=arabroot.io#setting-the-default-bind-address-for-containers) كانت تسمح للأجهزة على نفس الشبكة المحلية بالوصول إلى هذه المنافذ، وهذا سبب إضافي لتحديث Docker.
- عدم النشر إطلاقاً: لا يوجد سطر `ports`، وال Container يصل إليه فقط من يتصل بنفس شبكة Docker، وهذا ما نريده لكل تطبيق يعمل خلف Reverse Proxy ولكل قاعدة بيانات.

أما [expose](https://docs.docker.com/reference/compose-file/services/?ref=arabroot.io#expose) فلا ينشر أي شيء على السيرفر، وإنما يعلن أن الخدمة تستمع على منفذ معين، وهو توثيق داخل الملف أكثر منه إعداد، فال Containers على نفس الشبكة تصل إلى أي منفذ يستمع عليه التطبيق سواءً كتبته في `expose` أو لم تكتبه. لذلك إذا رأيت ملفاً فيه `expose: ["3000"]` فاعلم أن المنفذ غير منشور، وأن ال Reverse Proxy سوف يصل إليه عبر الشبكة.

## الشبكة المشتركة proxy: النمط الذي نستخدمه في الموقع

الآن نجمع كل ما سبق في نمط واحد، والفكرة كما يلي:

1. شبكة واحدة اسمها `proxy` ننشئها مرة واحدة خارج أي مشروع.
2. ال Reverse Proxy (NPM أو Traefik أو Caddy) هو الوحيد الذي ينشر منافذ على كل العناوين، وهي 80 و443 فقط.
3. كل تطبيق يخدمه ال Reverse Proxy يتصل بشبكة `proxy`، ولا ينشر أي منفذ.
4. قاعدة بيانات كل تطبيق على شبكة داخلية خاصة بمشروعه، ولا تتصل بشبكة `proxy` أبداً.
5. واجهات الإدارة على `127.0.0.1`، أو خلف ال Reverse Proxy بقائمة وصول Access List تسمح بشبكة ال VPN فقط.

والصورة التالية تبين هذا النمط على سيرفر فيه مشروعان:

![مخطط يوضح سيرفراً لا ينشر إلا المنفذين 80 و443 لل Reverse Proxy على شبكة proxy المشتركة، ويصل منها إلى التطبيقات بأسمائها، ولكل مشروع شبكة داخلية لقاعدة بياناته بدون طريق إلى الإنترنت، وواجهة الإدارة على 127.0.0.1](https://arabroot.io/content/images/2026/10/docker-networking-05-proxy-pattern.webp)

شبكة proxy مشتركة لل Reverse Proxy والتطبيقات، وشبكة داخلية لقاعدة بيانات كل مشروع

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

### إنشاء الشبكة وال Reverse Proxy

أنشئ الشبكة مرة واحدة على السيرفر:

```bash
docker network create proxy
```

وسوف نستخدم في المثال Caddy لأن ملف إعداده قصير، والنمط نفسه ينطبق على [Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/) و[Traefik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-traefik-reverse-proxy/) كما في دليل كل منهما، وتجد تثبيت Caddy بالتفصيل في [دليل Caddy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-caddy-reverse-proxy/). والملف `/opt/proxy/compose.yaml` سوف يكون كما يلي:

```yaml
services:
  caddy:
    image: caddy:2.11.4
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy

volumes:
  caddy_data:
  caddy_config:

networks:
  proxy:
    external: true
```

والملف `/opt/proxy/Caddyfile`:

```nginx
notes.example.com {
	reverse_proxy notes:80
}
```

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

- المفتاح `proxy` تحت `networks` أعلى الملف مع `external: true` يعني أن Compose يستخدم الشبكة التي أنشأناها كما هي، ولا يضيف إلى اسمها اسم المشروع.
- السطر `reverse_proxy notes:80` يستخدم اسم الخدمة `notes` ومنفذها داخل ال Container، وليس أي منفذ على السيرفر، وفي NPM تكتب نفس الاسم في حقل Forward Hostname.
- المنفذان 80 و443 هما الوحيدان المنشوران على كل العناوين في السيرفر كله.

### التطبيق وقاعدة بياناته

سوف نستخدم [whoami](https://github.com/traefik/whoami?ref=arabroot.io) مكان تطبيقك، فهو تطبيق صغير يعرض عناوينه وتفاصيل الطلب الذي وصله، ومعه قاعدة بيانات PostgreSQL. والملف `/opt/notes/compose.yaml` سوف يكون كما يلي:

```yaml
services:
  notes:
    image: traefik/whoami:v1.12.0
    restart: unless-stopped
    networks:
      - proxy
      - backend

  db:
    image: postgres:18.0-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: change-me
    networks:
      - backend

networks:
  proxy:
    external: true
  backend:
    internal: true
```

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

- لا يوجد سطر `ports` في الملف كله.
- التطبيق `notes` متصل بشبكتين: `proxy` حتى يصل إليه Caddy، و`backend` حتى يصل هو إلى قاعدة البيانات.
- قاعدة البيانات `db` على شبكة `backend` فقط، وهذه الشبكة `internal: true`، فلا يصل إليها Caddy ولا أي تطبيق آخر على السيرفر، ولا تخرج هي إلى الإنترنت.
- الشبكة `backend` ليست `external`، لذلك ينشئها Compose باسم `notes_backend` ويحذفها مع `docker compose down`، وكل مشروع آخر يستطيع أن يسمي شبكته `backend` دون أي تعارض.
- كلمة المرور هنا للمثال فقط، أما في الإنتاج فضعها في ملف `.env` وولدها بالأمر `openssl rand -hex 32`.

الآن شغل المشروعين، ثم اعرض ما على كل شبكة:

```bash
cd /opt/proxy && docker compose up -d
cd /opt/notes && docker compose up -d
docker network inspect proxy --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
docker network inspect notes_backend --format 'internal={{.Internal}} {{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
```

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

```bash
notes-notes-1 172.31.0.3/16
caddy 172.31.0.2/16

internal=true notes-notes-1 192.168.16.2/20
notes-db-1 192.168.16.3/20
```

ولاحظ أن قاعدة البيانات لا تظهر على شبكة `proxy` أبداً، وأن التطبيق له عنوانان، واحد في كل شبكة.

### لنتأكد أن العزل يعمل فعلاً

أولاً: هل يصل Caddy إلى التطبيق باسمه؟ نختبر ذلك من داخل Container ال Reverse Proxy نفسه، لأن هذا هو الطريق الذي سوف يسلكه كل طلب:

```bash
docker exec caddy wget -qO- http://notes/ | head -n 5
```

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

```bash
Hostname: 5ee6b2628a71
IP: 127.0.0.1
IP: ::1
IP: 192.168.16.2
IP: 172.31.0.3
```

ثانياً: هل يصل Caddy إلى قاعدة البيانات؟ يجب ألا يصل، لأنه ليس على شبكة `backend`:

```bash
docker exec caddy ping -c 1 -W 2 db
```

```bash
ping: bad address 'db'
```

ثالثاً: هل تخرج قاعدة البيانات إلى الإنترنت؟ يجب ألا تخرج، لأن شبكتها داخلية:

```bash
cd /opt/notes
docker compose exec db wget -q -T 5 -O /dev/null https://example.com
docker compose exec db ip route
```

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

```bash
wget: bad address 'example.com'
192.168.16.0/20 dev eth0 scope link  src 192.168.16.3
```

ولاحظ أن جدول التوجيه Routing Table في قاعدة البيانات فيه سطر واحد لشبكتها، ولا يوجد فيه مخرج افتراضي `default via`، وهذا معنى `internal: true`. وهذه الطبقة تحميك في حالتين: إذا تسرب منفذ قاعدة البيانات بالخطأ فلا يوجد له طريق من الخارج، وإذا اخترق أحد قاعدة البيانات فلا يستطيع أن يرسل البيانات منها إلى الإنترنت مباشرة.

⚠️

لا تجعل شبكة التطبيق نفسه `internal` إذا كان يحتاج الإنترنت، مثل إرسال البريد أو جلب التحديثات أو تسجيل الدخول عبر Google، والسبب أن التطبيق في هذا النمط يخرج إلى الإنترنت عبر شبكة `proxy`، أما شبكة `backend` فهي لقاعدة البيانات وأمثالها، مثل Redis، التي لا تحتاج إلا أن يكلمها التطبيق.

### ماذا لو نسيت إنشاء الشبكة؟

على سيرفر جديد، أو بعد استعادة نسخة احتياطية، قد تنسى الأمر `docker network create proxy`، وعندها يتوقف `docker compose up` بالخطأ التالي:

```bash
network proxy declared as external, but could not be found
```

والسبب أن `external: true` يعني أن Compose لا ينشئ هذه الشبكة أبداً، والحل أن تنشئها ثم تعيد الأمر، لذلك ضع سطر إنشائها في أول ملاحظات السيرفر أو في سكربت التجهيز، فهو أول ما تحتاجه عند النقل إلى سيرفر جديد.

### وماذا عن واجهات الإدارة؟

واجهات مثل [Portainer](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-portainer-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-docker/) ولوحة NPM نفسها لها طريقان آمنان، الأول أن تنشرها على `127.0.0.1` وتصل إليها بنفق SSH، والثاني أن تضعها على شبكة `proxy` بدون نشر أي منفذ، ثم تجعل ال Reverse Proxy يقبل طلباتها من شبكة ال VPN فقط، كما في [دليل WireGuard](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-%D9%85%D8%B9-wireguard-%D9%88-wg-easy/). أما نشرها على كل العناوين ثم الاعتماد على كلمة المرور وحدها فلا تقم به، والسبب أن من يتحكم في Portainer يتحكم في السيرفر كله.

## التحقق من النجاح

- الأمر `docker ps --format '{{.Names}}\t{{.Ports}}'` لا يعرض أي منفذ منشور على `0.0.0.0` أو `[::]` إلا 80 و443 الخاصين بال Reverse Proxy، وكل ما سواهما على `127.0.0.1` أو بدون نشر.
- الأمر `sudo ss -ltnp` على السيرفر يؤكد نفس النتيجة من جهة نظام التشغيل.
- الأمر `docker network inspect proxy` يعرض ال Reverse Proxy والتطبيقات فقط، ولا يعرض أي قاعدة بيانات.
- من Container ال Reverse Proxy يعمل `wget -qO- http://<service>/` لكل تطبيق، ويفشل `ping db`.
- من جهاز خارج السيرفر لا يستجيب أي منفذ غير 80 و443، وتستطيع التأكد بالأمر `nc -zv 203.0.113.10 5432` مثلاً.

## مشكلات شائعة وحلولها

### خطأ 502 أو 504 من ال Reverse Proxy

أشهر سبب لهذا الخطأ أن التطبيق ليس على شبكة `proxy`، كأن تنسى إضافتها إلى ملفه، أو تعيد إنشاءه من ملف قديم. ولنكسر الإعداد عمداً حتى نرى الخطأ، حيث سوف نفصل التطبيق عن الشبكة ثم نحاول الوصول إليه من Caddy:

```bash
docker network disconnect proxy notes-notes-1
docker exec caddy wget -qO- http://notes/
```

```bash
wget: bad address 'notes'
```

وفي المتصفح سوف يظهر `502 Bad Gateway`، وفي سجل Caddy سوف تجد السبب، وهو أن الاسم `notes` لم يعد له عنوان، وتنتهي الرسالة بعبارة `i/o timeout` أو `no such host` بحسب سيرفر DNS على جهازك:

```bash
docker logs caddy 2>&1 | grep -o '"msg":"dial tcp[^"]*"' | tail -n 1
```

```bash
"msg":"dial tcp: lookup notes: i/o timeout"
```

والحل أن تعيد التطبيق إلى الشبكة، والأفضل أن تصحح ملفه ثم تنفذ `docker compose up -d`، وأسباب 502 الأخرى هي منفذ خاطئ (المنفذ المنشور على السيرفر بدلاً من منفذ ال Container)، أو تطبيق يستمع على `127.0.0.1` داخل ال Container الخاص به بدلاً من `0.0.0.0`، وتجد تفصيلها في [دليل NPM](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/).

أما الخطأ 504 Gateway Timeout فيظهر غالباً مع Traefik عندما يتصل التطبيق بشبكتين، حيث قد يأخذ Traefik عنوان التطبيق من الشبكة الأخرى التي لا يتصل بها هو، فينتظر حتى تنتهي المهلة Timeout. والحل أن تحدد الشبكة في [إعداد مزود Docker](https://doc.traefik.io/traefik/reference/install-configuration/providers/docker/?ref=arabroot.io) بالخيار `providers.docker.network: proxy` كما في [دليل Traefik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-traefik-reverse-proxy/)، أو لكل تطبيق بالوسم `traefik.docker.network=proxy`.

### تعارض الأسماء على الشبكة المشتركة

اسم الخدمة على شبكة المشروع الخاصة لا يتعارض مع غيره، ولكن على شبكة `proxy` المشتركة تلتقي خدمات كل المشاريع، فإذا كان لديك مشروعان `blog` و`shop` وفي كل منهما خدمة اسمها `web` متصلة بشبكة `proxy`، فسوف يعيد DNS الخاص بالشبكة العنوانين معاً:

```bash
docker exec caddy nslookup web
```

```bash
Server:		127.0.0.11
Address:	127.0.0.11:53

Non-authoritative answer:
Name:	web
Address: 172.31.0.5
Name:	web
Address: 172.31.0.4
```

وبالتالي يذهب الطلب مرة إلى المدونة ومرة إلى المتجر، وهي من أغرب المشكلات حين تواجهها أول مرة، لأن الموقع يعمل ثم يتغير محتواه عند تحديث الصفحة. والحل أن يكون الاسم الذي يستخدمه ال Reverse Proxy فريداً على شبكة `proxy`، وذلك بأحد طريقين: أن تسمي الخدمة باسم التطبيق نفسه (`notes` وليس `app` أو `web`)، أو أن تضيف لها اسماً فريداً بال `aliases` كما شرحنا أعلاه، ثم تستخدم `blog-web` في إعداد ال Reverse Proxy. ولنفس السبب لا تضع قاعدة البيانات على شبكة `proxy`، فلو فعلت ذلك في مشروعين لأصبح الاسم `db` يشير إلى قاعدتين مختلفتين.

### ال Container على شبكتين والمخرج الافتراضي خاطئ

ال Container المتصل بشبكتين له مخرج افتراضي Default Gateway واحد، ومنه تخرج كل حركة لا تتجه إلى إحدى الشبكتين، و[التوثيق يقول](https://docs.docker.com/engine/network/?ref=arabroot.io#connecting-to-multiple-networks) إن Docker هو من يختار هذا المخرج، وقد يتغير اختياره كلما تغيرت شبكات ال Container. وفي أغلب التطبيقات لا يهمك من أين تخرج الحركة، ولكن في تطبيق مثل wg-easy الذي يمرر حركة عملاء ال VPN إلى الإنترنت فالأمر مهم، حيث تنجح المصافحة ولا يعمل الإنترنت داخل النفق، كما شرحنا في [دليل WireGuard](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-%D9%85%D8%B9-wireguard-%D9%88-wg-easy/). والحل خياران في Compose: [gw\_priority](https://docs.docker.com/reference/compose-file/services/?ref=arabroot.io#gw%5Fpriority) (من Compose 2.33.1) لتحديد الشبكة التي يكون منها المخرج الافتراضي، و[interface\_name](https://docs.docker.com/reference/compose-file/services/?ref=arabroot.io#interface%5Fname) (من Compose 2.36.0) لتثبيت اسم واجهة الشبكة Network Interface لكل شبكة:

```yaml
services:
  vpn:
    image: alpine:3.24
    command: sleep infinity
    networks:
      wg:
        interface_name: eth0
        gw_priority: 1
      proxy:
        interface_name: eth1

networks:
  proxy:
    external: true
  wg:
    ipam:
      config:
        - subnet: 10.42.42.0/24
```

```bash
docker compose exec vpn ip route
```

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

```bash
default via 10.42.42.1 dev eth0
10.42.42.0/24 dev eth0 scope link  src 10.42.42.2
172.31.0.0/16 dev eth1 scope link  src 172.31.0.3
```

ولاحظ أن المخرج الافتراضي عبر `eth0` وهي شبكة `wg`، أما شبكة `proxy` فلها سطر واحد على `eth1` للوصول إلى ال Containers عليها فقط. والقيمة الافتراضية ل `gw_priority` هي 0، والشبكة صاحبة القيمة الأعلى هي المخرج، لذلك يكفي أن تعطي الشبكة التي تريدها القيمة 1.

### نفاد نطاقات العناوين Address Pools

كل شبكة تنشئها، وكل مشروع Compose بشبكته الافتراضية، يأخذ نطاقاً من العناوين، و[النطاقات الافتراضية في Docker](https://docs.docker.com/engine/network/?ref=arabroot.io#automatic-subnet-allocation) هي `172.17.0.0/16` حتى `172.31.0.0/16` بحجم `/16` لكل شبكة، ثم `192.168.0.0/16` مقسمة إلى شبكات بحجم `/20`، أي نحو 31 شبكة فقط. فإذا كان على سيرفرك مشاريع كثيرة، وكل مشروع ينشئ شبكتين أو ثلاثاً، فسوف تنفد النطاقات ويفشل إنشاء أي شبكة جديدة بالخطأ `all predefined address pools have been fully subnetted`.

وهناك مشكلة أخرى في نفس النطاقات تخص المختبر المنزلي وال VPN، وهي أن Docker قد يأخذ نطاقاً مثل `192.168.0.0/20` وشبكة منزلك أو مكتبك على `192.168.1.0/24`، فيصبح السيرفر غير قادر على الرد على الأجهزة في هذه الشبكة لأنه يظن أنها داخل Docker. والحل في الحالتين أن تحدد النطاقات بنفسك في `/etc/docker/daemon.json` بالمفتاح `default-address-pools`، وبشبكات أصغر بحجم `/24` يتسع النطاق الواحد `/16` ل 256 شبكة:

```bash
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  },
  "live-restore": true,
  "default-address-pools": [
    { "base": "172.17.0.0/16", "size": 24 },
    { "base": "172.18.0.0/16", "size": 24 }
  ]
}
EOF
sudo dockerd --validate --config-file /etc/docker/daemon.json
sudo systemctl restart docker
```

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

- الملف يكمل إعداد السجلات و`live-restore` من [دليل التثبيت](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-%D8%B9%D9%84%D9%89-ubuntu/)، فإذا كان لديك ملف مختلف فأضف المفتاح `default-address-pools` إليه ولا تستبدله.
- الحجم `24` يعني أن كل شبكة تأخذ 254 عنواناً، وهذا يكفي أي مشروع على سيرفر واحد.
- اختر نطاقات لا تتداخل مع شبكة المنزل أو المكتب أو ال VPN التي يصل منها المستخدمون.
- الإعداد الجديد يسري على الشبكات الجديدة فقط، أما الموجودة فتبقى على نطاقها حتى تحذفها وتنشئها من جديد، أي `docker compose down` ثم `up -d` لكل مشروع، وأعد إنشاء `proxy` بعد إيقاف كل من يستخدمها.

### ملاحظات IPv6

شبكات bridge التي تنشئها لا تحصل على عناوين IPv6 إلا إذا [فعلت ذلك](https://docs.docker.com/engine/daemon/ipv6/?ref=arabroot.io) بالخيار `--ipv6` أو `enable_ipv6: true` في Compose، وهذا يكفي في أغلب الحالات، لأن ال Reverse Proxy هو من يستقبل الزائر. ولكن انتبه لحالة واحدة: إذا كان لنطاقك سجل `AAAA` وشبكة ال Reverse Proxy بدون IPv6، فإن Docker [يمرر طلبات IPv6 عبر ال userland-proxy](https://docs.docker.com/engine/network/port-publishing/?ref=arabroot.io) إلى عنوان IPv4 الخاص بال Container، وبالتالي يرى ال Reverse Proxy كل زوار IPv6 قادمين من عنوان داخلي واحد، فتضيع عناوينهم الحقيقية في السجلات وفي قوائم الوصول. والحل إما أن تفعل IPv6 في شبكة `proxy` نفسها، وإما ألا تضيف سجل `AAAA` حتى تفعله، وتجد أثر ذلك على التطبيقات في دليل [تطبيقك خلف 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/).

### وماذا عن HAProxy أو Reverse Proxy خارج Docker؟

إذا كان ال Reverse Proxy مثبتاً على السيرفر نفسه وليس في Container، كما في طريقة التثبيت من حزمة Ubuntu التي يذكرها [دليل HAProxy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-haproxy-load-balancer/) بديلاً عن ال Container، أو كان يعمل على سيرفر آخر يوزع الطلبات على عدة سيرفرات، فلا يستطيع أن يستخدم أسماء ال Containers، والسبب أن DNS المدمج يعمل داخل شبكات Docker فقط، وفي هذه الحالة انشر منفذ كل تطبيق على `127.0.0.1` فقط واجعل ال Reverse Proxy يتصل به هناك. وإذا كنت تقارن بين هذه الأدوات فراجع [المقارنة بين Nginx Proxy Manager وTraefik وHAProxy](https://arabroot.io/articles/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-nginx-proxy-manager-%D9%88-traefik-%D9%88-haproxy/).

## الخلاصة

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

- المنفذ المنشور بدون عنوان مفتوح على الإنترنت ويتجاوز `ufw`، فلا تنشر على كل العناوين إلا 80 و443 الخاصين بال Reverse Proxy.
- الشبكة الافتراضية `bridge` لا توفر أسماء ولا عزلاً، أما الشبكة التي تنشئها أنت، ومنها شبكة كل مشروع Compose، فتوفر الاثنين.
- أنشئ شبكة `proxy` مرة واحدة، وصل بها ال Reverse Proxy والتطبيقات فقط بالإعداد `external: true`، دون أي منفذ منشور للتطبيقات.
- ضع قاعدة بيانات كل مشروع على شبكة خاصة به بالإعداد `internal: true`، ولا تصلها بشبكة `proxy` أبداً.
- اجعل الاسم الذي يستخدمه ال Reverse Proxy فريداً على الشبكة المشتركة، وثبت المخرج الافتراضي ب `gw_priority` عندما يهمك من أين تخرج الحركة.
- حدد `default-address-pools` بنطاقات لا تتداخل مع شبكاتك قبل أن تكثر المشاريع على السيرفر.

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

- أكتوبر 2026: كتابة الدليل واختبار أمثلته على Docker Engine 29.8.0 وDocker Compose v5.5.1.