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

كيف تصل حاوية Gluetun إلى المضيف والحاويات؟

تعرّف إلى سبب ظهور خطأ نشر المنافذ مع network_mode: service:gluetun، وكيف تنشر المنافذ على Gluetun وتسمح فقط بالشبكات المطلوبة خارج النفق.

ماذا يحدث عندما تنضم حاوية إلى شبكة gluetun

لا تملك الحاوية التي تُحدِّد network_mode: service:gluetun واجهات شبكة خاصة بها. بل تنضم إلى مساحة أسماء الشبكة الخاصة بـgluetun، ولذلك لا يعود نشر المنافذ وقواعد الجدار الناري من خصائص تلك الحاوية، بل يصبحان من خصائص خدمة gluetun. وتستند كل إجابة أدناه إلى هذه الحقيقة وحدها.

مساحة أسماء الشبكة هي نسخة خاصة من مكدس الشبكة في النواة: لها واجهاتها الخاصة، وجدول التوجيه الخاص بها، وقواعد الجدار الناري الخاصة بها، ومقابس الاستماع الخاصة بها. يمنح Docker كل حاوية مساحةً كهذه افتراضياً. وعندما تكتب network_mode: service:gluetun، يتجاوز Docker هذه الخطوة ويضع الحاوية الجديدة داخل مساحة الأسماء التي تملكها gluetun مسبقاً. تحتفظ الحاوية بنظام ملفاتها الخاص وملف /etc/hosts الخاص بها، وتكتسب هذه النقطة الثانية أهمية لاحقاً.

يمكنك رؤية ذلك مباشرةً.

docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrent

يعرض ذلك container: متبوعاً بمعرّف حاوية gluetun، بينما كانت الحاوية العادية ستعرض bridge. يتابع هذا الدليل من حيث ينتهي توجيه حركة Docker عبر VPN باستخدام gluetun: النفق يعمل، والآن لا يستطيع أي شيء الاتصال بالحاوية.

انشر المنفذ على gluetun، وليس على التطبيق

اترك كتلة ports: في الخدمة التي تضبط network_mode، وسيرفض Docker إنشاء الحاوية:

Error response from daemon: conflicting options: port publishing and the container type network mode

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

services:
  gluetun:
    ports:
      - "8080:8080/tcp"   # qBittorrent web UI

  qbittorrent:
    network_mode: "service:gluetun"
    # no ports: block here

وجود كتلة expose: في الخدمة التابعة لا فائدة منه أيضاً، ووجود كتلة networks: فيها يوقف التشغيل تماماً: يذكر Compose أن الخدمة تعرّف network_mode وnetworks المتعارضين، ويرفض تحميل الملف بالكامل.

تظهر إحدى النتائج لاحقاً. تشترك كل حاوية في مساحة الأسماء نفسها في مساحة منافذ واحدة، لذلك تتعارض تطبيقات تستخدم 8080 افتراضياً، ويفشل التطبيق الثاني الذي يبدأ برسالة تفيد بأن العنوان قيد الاستخدام. غيّر قيمة أحدهما في إعداداته الخاصة، مثل متغير WEBUI_PORT في صورة LinuxServer qBittorrent، ثم انشر الرقم الجديد على gluetun.

كيف تصل الحاويات خلف gluetun إلى بعضها؟

داخل مساحة الأسماء، تشترك الحاويات مسبقاً في واجهة loopback. تصل الحاوية الموجودة خلف gluetun إلى الحاوية الشقيقة على 127.0.0.1:<port> من دون استخدام شبكة Docker.

من خارج مساحة الأسماء، لا يكون للحاوية اسم. يحل DNS المضمّن في Docker اسم الخدمة إلى عنوان تلك الخدمة على شبكة يعرّفها المستخدم، لكن هذه الحاوية لا تملك عنواناً على أي شبكة. لذلك لا تصل حاوية عادية مثل Sonarr إلى عميل التورنت على http://qbittorrent:8080. بل تصل إليه على http://gluetun:8080، لأن المقبس يستمع في مساحة أسماء gluetun وعلى عنوان gluetun. يفاجئ ذلك من يعرفون كيفية عمل شبكات Docker Compose وأسماء الخدمات ويتوقعون تطبيق التسمية المعتاد. ويعمل ذلك أيضاً من دون نشر أي منفذ على المضيف، لأن الحاويتين موجودتان على شبكة Compose نفسها.

تحقق من DNS قبل تصحيح أي مشكلة أخرى. يشغّل Gluetun محلل الأسماء الخاص به ويعيد كتابة /etc/resolv.conf داخل حاويته، لكن /etc/resolv.conf ملف خاص بكل حاوية، لذلك فالملف الذي كتبه gluetun ليس الملف الذي يقرأه تطبيقك.

docker exec qbittorrent cat /etc/resolv.conf

كيف أصل إلى خدمة تعمل على مضيف Docker؟

استخدم host.docker.internal. يتطلب ذلك إعدادين في موضعين مختلفين، لأن هناك مشكلتين مختلفتين.

يأتي الاسم أولاً. /etc/hosts خاص بكل حاوية، لذلك يجب أن يكون إدخال extra_hosts على حاوية التطبيق، وليس على gluetun.

  prowlarr:
    network_mode: "service:gluetun"
    extra_hosts:
      - "host.docker.internal:host-gateway"

host-gateway قيمة خاصة يستبدلها Docker بعنوان داخلي للمضيف نفسه. في تثبيت Docker عادي على Linux، يكون هذا عنوان جسر docker0، وغالباً ما يكون 172.17.0.1. تحقّق من العنوان لديك باستخدام ip -4 addr show docker0 على VPS. يحل Docker Desktop هذا الاسم بنفسه، ولذلك تتخطى الأدلة المكتوبة لجهاز محمول سطر extra_hosts، ثم يفشل الملف نفسه على خادم.

يأتي المسار ثانياً. إضافة الاسم تخبر الحاوية فقط بالعنوان الذي تستخدمه. تظل الحزمة تخرج عبر المسار الافتراضي لـ gluetun، وهو النفق، ثم يحظرها جدار gluetun الناري. يتمثل العَرَض في اتصال يظل معلقاً ثم تنتهي مهلته، وليس في اتصال مرفوض. الرفض يعني أن الحزمة وصلت وأن شيئاً ما ردّ بالرفض. انتهاء المهلة يعني أنها لم تصل.

  gluetun:
    environment:
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32

تحقّق بعد ذلك من أن خدمة المضيف تستمع فعلاً على ذلك العنوان. لا يمكن الوصول إلى خادم PostgreSQL المرتبط بـ 127.0.0.1 فقط من أي حاوية، سواء عبر نفق أم لا، لأن 127.0.0.1 داخل مساحة الأسماء هو loopback الخاص بمساحة الأسماء نفسها. اربطه بـ 172.17.0.1 بدلاً من ذلك: عندها يقبل الاتصالات من الحاويات، مع بقائه غير مكشوف على الواجهة العامة. تحقّق باستخدام ss -lntp | grep 5432 على المضيف.

ما الذي يغيّره FIREWALL_OUTBOUND_SUBNETS فعلياً

تصف وثائق gluetun هذه القيمة بأنها الشبكات الفرعية المفصولة بفواصل التي يُسمح لـgluetun والحاويات التي تشارك مكدس الشبكة الخاص به بالوصول إليها، وتوضح أنها تتضمن تغييرات على جدار الحماية والتوجيه. كلا الجانبين مهم. يضيف gluetun مساراً لكل شبكة فرعية مدرجة عبر بوابة Docker bridge، لذلك تخرج الحزم المتجهة إلى تلك العناوين عبر eth0 بدلاً من النفق. كما يفتح جدار الحماية أمامها، لأن gluetun يسقط بخلاف ذلك حركة المرور الصادرة التي لا تتجه إلى خادم VPN.

اكتب القيمة من دون مسافات بعد الفواصل.

      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32

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

الوصول إلى واجهة الويب من نظير Tailscale

يمنح Tailscale كل جهاز عنواناً ضمن 100.64.0.0/10، وهو النطاق المحجوز لـ carrier-grade NAT. لا تترتب أي تكلفة على ذلك في tailnet شخصية، لكن حدود المستخدمين والأجهزة في الخطة المجانية تحدد ما إذا كان هذا سيبقى صحيحاً عندما يحتاج أشخاص آخرون إلى الوصول إلى واجهات المستخدم نفسها. وبما أن الفوترة تحسب المستخدمين لا الأجهزة، فإن التكلفة الفعلية لـ tailnet مدفوعة تعتمد على عدد الأشخاص الذين تدعوهم، لا على عدد الحاويات التي تتيح لهم الوصول إليها. ويتطلب الاتجاهان عملاً مختلفاً.

الوصول الوارد هو الأبسط. يؤدي نشر 8080:8080 على gluetun إلى ربط ذلك المنفذ بجميع عناوين المضيف، وتُعد واجهة tailscale0 في المضيف أحد هذه العناوين، لذلك يفتح النظير http://<machine-name>:8080 ويصل إلى الحاوية. لا يشارك Gluetun في هذا المسار، لأن قاعدة NAT الخاصة بـDocker توجد على المضيف، خارج مساحة أسماء الشبكة.

لجعل واجهة الويب متاحة عبر tailnet فقط، اربط المنفذ المنشور بعنوان Tailscale الخاص بالمضيف بدلاً من ربطه بجميع العناوين.

    ports:
      - "100.101.102.103:8080:8080/tcp"

اعثر على ذلك العنوان باستخدام tailscale ip -4 على المضيف. ويُعد الربط هنا وسيلة تحكم أقوى من قاعدة جدار ناري، لأن المنفذ لا يُفتح على الواجهة العامة أصلاً. إذا كنت تفضل الوصول إلى واجهة الويب عبر اسم HTTPS بدلاً من المضيف والمنفذ، فيمكن لـtailscale serve أن يمرر الطلبات إلى ذلك المنفذ المنشور، لكن serve وfunnel يختلفان في الجهة التي يمكنها الوصول إليها في النهاية، ولا يُبقي واجهة الويب ضمن tailnet إلا أحدهما. كما يتجاوز ذلك المشكلة الموضحة في نشر Docker للمنافذ متجاوزاً ufw مباشرةً.

يظهر FIREWALL_OUTBOUND_SUBNETS عند الوصول الصادر. إذا احتاجت حاوية إلى الاتصال بنظير، فأضف عنوان ذلك النظير، وفضّل استخدام /32 لكل نظير بدلاً من استخدام /10 بأكمله. إذا كان الجهاز الذي تتصل به موجوداً على شبكة خاصة يمكن الوصول إليها عبر VPS يعلن عن تلك الشبكة الفرعية إلى tailnet، فأدرج النطاق المعلن بدلاً من عنوان 100.x الخاص بالموجّه، وتأكد من أن المضيف نفسه قبل تلك المسارات. لن تُحل أسماء MagicDNS داخل الحاوية، لأن الحاوية لا تستخدم محلل الأسماء لدى المضيف، لذلك استخدم عنوان 100.x الرقمي أو ثبّته باستخدام سطر extra_hosts. وينطبق الأمر نفسه عند تشغيل خادم التحكم الخاص بك في Tailscale باستخدام Headscale.

ملف Compose كامل للبنية الشائعة

عميل تنزيل يعمل خلف VPN، وواجهتا ويب لا تستجيبان إلا عبر tailnet، وحاوية واحدة تقرأ قاعدة بيانات PostgreSQL تعمل على المضيف.

services:
  gluetun:
    image: qmcgaw/gluetun:latest
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - "127.0.0.1:8000:8000/tcp"        # gluetun control server, host only
      - "100.101.102.103:8080:8080/tcp"  # qBittorrent UI, tailnet only
      - "100.101.102.103:9696:9696/tcp"  # Prowlarr UI, tailnet only
    volumes:
      - ./gluetun:/gluetun
    environment:
      - VPN_SERVICE_PROVIDER=mullvad
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
      - WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
      - SERVER_CITIES=Amsterdam
      - FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,100.64.7.9/32
      - TZ=Europe/Amsterdam
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    network_mode: "service:gluetun"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent:/config
      - /srv/downloads:/downloads
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    network_mode: "service:gluetun"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - PROWLARR__POSTGRES__HOST=host.docker.internal
      - PROWLARR__POSTGRES__PORT=5432
      - PROWLARR__POSTGRES__USER=prowlarr
      - PROWLARR__POSTGRES__PASSWORD=${PROWLARR_DB_PASSWORD}
      - PROWLARR__POSTGRES__MAINDB=prowlarr-main
      - PROWLARR__POSTGRES__LOGDB=prowlarr-log
    volumes:
      - ./prowlarr:/config
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

اقرأ الملف لفهم النمط، وليس أسماء المنتجات. تُنشر واجهتا الويب على gluetun، وتُربطان بعنوان tailnet الخاص بالمضيف، لذلك تستجيبان عبر Tailscale فقط. يحتوي Prowlarr وحده على السطر extra_hosts، لأن Prowlarr هو الحاوية التي تحل أسماء host.docker.internal. يعرّف FIREWALL_OUTBOUND_SUBNETS عنوانين منفردين: عنوان Docker bridge الخاص بالمضيف لكي يتمكن Prowlarr من فتح اتصال بقاعدة البيانات، وعنوان أحد أقران tailnet.

يغيب خادم PostgreSQL عمداً عن الملف. فهو يعمل على VPS كخدمة نظام عادية وتستمع الخدمة على 172.17.0.1:5432. هذه هي الطبقات نفسها الموجودة في مكدس arr على Docker Compose، مع نقل قاعدة البيانات إلى خارج Docker.

أبقِ المفتاح الخاص لـWireGuard خارج ملف Compose. يقرأ ${WIREGUARD_PRIVATE_KEY} المفتاح من ملف .env بجواره، وفق النمط المشروح في ملفات البيئة والأسرار في Docker Compose. يستخدم الشرط condition: service_healthy healthcheck المضمّن أصلاً في صورة gluetun، لذلك لا تبدأ أي حاوية حتى يعلن النفق أنه يعمل. تشرح فحوصات الصحة في Compose البنية العامة.

النشر على كل عنوان بدلاً من tailnet فقط

احذف بادئة العنوان وعمليات ربط المنافذ في 0.0.0.0، إذ يشمل ذلك عنوان VPS العام. نفّذ ذلك فقط خلف جدار ناري تتحكم فيه، واقرأ ملاحظة ufw أعلاه أولاً.

    ports:
      - "8080:8080/tcp"

تحقّق من أن النفق ما زال ينقل حركة الشبكة

نفّذ الطلب نفسه مرتين: مرة من داخل مساحة الأسماء، ومرة من المضيف، ثم قارن النتيجتين.

docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.org

يجب أن يعرض الطلب الأول عنوان الخروج الخاص بموفّر VPN. ويجب أن يعرض الطلب الثاني عنوان VPS. إذا تطابق العنوانان، فهذا يعني أن حركة الشبكة من الحاوية لا تمر عبر النفق. لن تفيد أي معالجة في هذا الدليل قبل تصحيح ذلك.

يعرض جدول التوجيه ما يمر خارج النفق وما لا يمر به.

docker run --rm --network=container:gluetun alpine:3.22 ip route show

يجب أن يشير المسار الافتراضي إلى واجهة النفق، tun0. ويجب أن ترى تحته مساراً واحداً لكل إدخال في FIREWALL_OUTBOUND_SUBNETS، بحيث يشير إلى البوابة الخاصة بجسر Docker. وأي مسار آخر يخرج عبر eth0 هو حركة شبكة تتجاوز VPN.

يعرض خادم التحكم في Gluetun عنوان IP العام نفسه على المنفذ 8000، في /v1/publicip/ip. تتطلب الإصدارات الحديثة إعداد المصادقة لمسارات خادم التحكم، لذا أعدّها قبل الاعتماد عليها.

الثغرة التي ينشئها نطاق فرعي واحد خاطئ

FIREWALL_OUTBOUND_SUBNETS هي فتحة تفتحها عمداً في جدار الحماية، لذلك يساوي حجم الفتحة حجم الخطر. فيما يلي أربع طرق لجعلها أكبر من اللازم:

  • 0.0.0.0/0 يرسل كل شيء خارج النفق. يكتشف فحصا IP أعلاه ذلك عند التشغيل الأول، لأنهما سيعرضان العنوان نفسه.
  • نطاق أوسع من الهدف. يؤدي فتح 10.0.0.0/8 للوصول إلى جهاز واحد على 10.0.1.7 إلى فتح كل عنوان قد يعلنه نظير من نظائر torrent ضمن ذلك النطاق. اكتب 10.0.1.7/32.
  • نطاق يتداخل مع عناوين النفق نفسه. تحذّر وثائق gluetun من أن ذلك يجعل gluetun يرسل حركة VPN عبر bridge بدلاً من النفق، مما يعطّل إعادة توجيه المنافذ. تحقّق من قيمة WIREGUARD_ADDRESSES قبل فتح أي نطاق خاص.
  • 100.64.0.0/10 في Tailscale. يؤدي ذلك إلى فتح نحو أربعة ملايين عنوان حتى يمكن الوصول إلى نظير واحد. أدرج النظائر المطلوبة كإدخالات /32.

تذكّر أن هذا الإعداد ينطبق على مساحة الأسماء بأكملها. يؤدي فتح نطاق فرعي حتى يتمكن indexer من الوصول إلى خدمة على المضيف إلى فتح النطاق نفسه لعميل torrent الذي يشارك مساحة الأسماء هذه. أعد فحص IP العام بعد كل تغيير لهذا المتغير، لأنه الاختبار الوحيد الذي يبيّن ما إذا كان التغيير قد حقق الغرض المقصود.

ما الذي يتعطل عند إعادة تشغيل gluetun

يمتلك gluetun مساحة الأسماء، لذلك يرتبط عمر gluetun التشغيلي بعمر مساحة الأسماء. تفشل حاوية تعتمد عليه فوراً إذا بدأت تشغيلها أثناء توقف gluetun:

Error response from daemon: cannot join network of a non running container

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

docker compose up -d --force-recreate

وينطبق الأمر نفسه على تحديثات الصور. يؤدي سحب صورة gluetun جديدة وإعادة إنشاء هذه الخدمة وحدها إلى ترك الخدمات الأخرى متصلة بمساحة أسماء لم تعد موجودة.

FAQ

لماذا يعرض Docker الرسالة "نشر المنافذ ووضع شبكة الحاوية"؟

لأن كتلة ports: ما زالت موجودة في خدمة تضبط أيضاً network_mode: service:gluetun. يؤدي نشر منفذ إلى إضافة قاعدة NAT تعيد توجيه منفذ على المضيف إلى مساحة أسماء الشبكة الخاصة بالحاوية، لكن الحاوية في هذا الوضع لا تملك مساحة أسماء شبكة خاصة بها. احذف كتلة ports: من تلك الخدمة، وأضف التعيين نفسه إلى خدمة gluetun. يبقى رقم المنفذ كما هو، لأن التطبيق ما زال يستمع إليه داخل مساحة أسماء الشبكة المشتركة.

كيف تصل الحاويات الأخرى إلى خدمة تعمل خلف gluetun؟

تصل الحاويات داخل مساحة أسماء الشبكة نفسها إلى بعضها عبر 127.0.0.1. أما الحاويات خارجها فتستخدم اسم خدمة gluetun، ولذلك يعمل http://gluetun:8080 بينما لا يعمل http://qbittorrent:8080. لا تملك حاوية التطبيق عنواناً على أي شبكة Docker، ولذلك لا يجد خادم DNS المضمّن عنواناً يحل إليه اسمها. لا حاجة إلى نشر منفذ لهذا الغرض، ما دامت الحاويتان تشتركان في شبكة Compose.

ماذا أضع في FIREWALL_OUTBOUND_SUBNETS؟

أضف فقط العناوين التي يجب على حاوية تعمل خلف gluetun بدء اتصال بها، واكتبها بأضيق نطاق ممكن. يُكتب عنوان جهاز واحد بصيغة /32. الإدخالان الشائعان هما مضيف Docker بالصيغة 172.17.0.1/32، وإدخال /32 واحد لكل نظير Tailscale تتصل به. لا تضف 0.0.0.0/0 مطلقاً، ولا تضف نطاقاً يتداخل مع عناوين نفق VPN نفسه. لا تحتاج الاتصالات الواردة إلى منفذ منشور إلى إدخال هنا.

لماذا لا تستطيع الحاوية حل أسماء Tailscale MagicDNS؟

يعمل MagicDNS بتوجيه محلل الأسماء في المضيف إلى خادم DNS الخاص بـTailscale، لكن الحاوية لا تستخدم محلل الأسماء في المضيف. بل تستخدم ما يحدده /etc/resolv.conf الخاص بها، وهو إعداد DNS الخاص بـgluetun عند تشغيلها خلفه. تحقّق باستخدام docker exec <container> cat /etc/resolv.conf. استخدم عنوان 100.x الرقمي للنظير، أو ثبّت الاسم بإدخال extra_hosts في تلك الحاوية.

كيف أتأكد من أن حركة الشبكة ما زالت تمر عبر VPN؟

نفّذ طلباً واحداً من داخل مساحة أسماء الشبكة، ونفّذ الطلب نفسه من المضيف، ثم قارن الإجابتين. يجب أن يعرض docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org عنوان الخروج الخاص بموفر VPN، بينما يعرض curl -s https://api.ipify.org على VPS عنوان VPS. تعني الإجابتان المتطابقتان أن النفق لا ينقل حركة شبكة الحاوية. أعد هذا الفحص بعد كل تغيير على FIREWALL_OUTBOUND_SUBNETS.