SSD Nodes Learn 🎉 VPS من $4.99/شهر
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-07

توجيه حاويات Docker عبر VPN باستخدام Gluetun

عند استخدام Gluetun مع network_mode: service، تختفي المنافذ المنشورة واسم الخدمة. تعرّف على السبب وملف Compose الصحيح لنشر المنافذ.

لماذا تختفي المنافذ عند توجيه حاويات Docker عبر VPN

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

إذا تركت كتلة ports: في الحاوية المرتبطة، فسيرفض Docker إنشاءها أصلاً:

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

الأداة المستخدمة هنا هي Gluetun، وهي حاوية تتصل بموفّر VPN تجاري عبر WireGuard أو OpenVPN، وتطبّق جداراً نارياً خاصاً بها. الإصدار v3.41.3 هو الإصدار الحالي في أغسطس 2026. تستخدم الأمثلة Mullvad مع WireGuard، لذلك تحتاج إلى حساب ومفتاح من موفّر الخدمة. إذا كنت تفضّل إنهاء النفق على أجهزة تملكها، فإن تشغيل خادم WireGuard خاص بك على VPS ينشئ الطرف الآخر، بينما يضيف wg-easy في Docker واجهة ويب إلى هذا الإعداد.

ما الذي يفعله network_mode: "service:gluetun" فعلياً

يحصل كل Docker container عادةً على network namespace خاص به، يضم واجهاته وجدول التوجيه وقواعد جدار الحماية وlistening sockets الخاصة به. يتجاوز الوضع service: هذه الخطوة، ويبدأ الـcontainer داخل namespace الخاص بـgluetun. يعني وجود namespace واحد استخدام عنوان IP واحد، وهذا يغيّر ستة أمور.

  • لا يملك التطبيق عنواناً خاصاً به. عنوانه هو عنوان gluetun.
  • لا يتصل التطبيق بأي Docker network، لذلك لا يُسجَّل اسم الخدمة ولا يمكن حله. يجب على الـcontainers الأخرى استخدام gluetun.
  • تتصل الـcontainers الموجودة داخل namespace نفسه ببعضها عبر localhost.
  • لا يمكن لـcontainerين داخل namespace واحد الاستماع على المنفذ نفسه. توضح وثائق Gluetun ذلك صراحةً: لا يوجد حل بديل.
  • ترتبط capabilities بـcontainer، لا بـnamespace. يحتفظ Gluetun بـNET_ADMIN و/dev/net/tun لأنه ينشئ tunnel interface. ولا يرث الـcontainer المرتبط هذه capabilities.
  • يرفض Compose أي ملف تضبط فيه خدمة واحدة كلاً من network_mode وnetworks. صِل gluetun بشبكاتك، وسينضم التطبيق إليها عبره.

تؤدي إعادة تشغيل gluetun إلى قطع اتصال كل ما هو مرتبط به. هذا سلوك موثّق، ولذلك يعيد gluetun تشغيل عملية VPN داخل الـcontainer بدلاً من الخروج عند فشل الاتصال. بعد إعادة تشغيل gluetun أو إعادة إنشائه يدوياً، أعد تشغيل الـcontainers المرتبطة به.

ملف Compose الذي يعمل

services:
  gluetun:
    image: qmcgaw/gluetun:v3
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - VPN_SERVICE_PROVIDER=mullvad
      - VPN_TYPE=wireguard
      - SERVER_CITIES=Amsterdam
      - TZ=Europe/Amsterdam
    env_file:
      - ./gluetun.env
    volumes:
      - ./gluetun:/gluetun
    ports:
      - 127.0.0.1:8080:8080/tcp
    restart: unless-stopped

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

الوسم :v3 هو أحدث إصدار مستقر في السلسلة v3. أما الوسم :latest فيشير إلى آخر commit في فرع master، وهو فرع التطوير التجريبي. لذلك ثبّت :v3 على جهاز لا تريد أن تقضي يوم الثلاثاء في تصحيح أخطائه.

يجب أن يطابق WEBUI_PORT=8080 المنفذ المنشور، لأن qBittorrent يستمع داخل مساحة أسماء gluetun، وقاعدة النشر ترسل حركة المرور الواردة من الخادم إلى المنفذ 8080 داخلها. إذا غيّرت أحد الرقمين دون الآخر، فلن يستجيب المنفذ لأي شيء. يحصر 127.0.0.1:8080:8080 واجهة الويب في عنوان loopback المحلي للخادم. أما 8080:8080 من دون عنوان محدد، فينشر المنفذ على جميع الواجهات وينشئ قاعدة جدار ناري خاصة به. وهذا ما يجعل المنافذ التي ينشرها Docker تتجاوز ufw مباشرة.

شغّل الحاويات، ثم تحقّق منها بهذا الترتيب:

docker compose up -d
docker compose ps
docker compose logs gluetun | tail -30

يجب أن يعرض docker compose ps gluetun بالحالة healthy، وqbittorrent بالحالة running. ثم تحقّق من عنوان الخروج من داخل مساحة الأسماء. هذا هو الاختبار الذي يحدد نتيجة جميع الاختبارات الأخرى:

docker run --rm --network=container:gluetun alpine:3.22 sh -c "apk add wget && wget -qO- https://ipinfo.io"

يجب أن يحتوي الحقل ip في JSON على عنوان موفّر VPN لديك. إذا كان العنوان هو عنوان خادمك نفسه، فالتطبيق ليس داخل النفق، ولن يعمل أي شيء أدناه كما هو موضح.

أبقِ المفاتيح خارج ملف Compose

يحتوي gluetun.env على بيانات الاعتماد، ويبقى خارج git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

تأتي القيمتان من ملف إعداد WireGuard تنشئه في منطقة حسابك لدى مزوّد الخدمة. اضبط صلاحيات الملف على 600. كن واضحاً بشأن الفائدة الفعلية: يبقى المفتاح خارج مستودعك، لكن docker inspect gluetun يطبع مع ذلك كل متغيرات البيئة لأي شخص يستطيع الوصول إلى Docker socket. يشرح ملفات البيئة والأسرار في Docker Compose الخيارات الأقوى.

كيفية اتصال حاوية خارج النفق بحاوية داخله

يعمل الاتصال في الاتجاهين، ويستخدم كل اتجاه اسماً مختلفاً. تحتاج الحاويتان إلى شبكة Docker مشتركة، أي شبكة gluetun، لأن الحاوية المرتبطة به لا تملك شبكة خاصة بها. يشرح كيفية توصيل شبكات Docker Compose الإعدادات الافتراضية.

للاتصال من الخارج إلى الداخل، استخدم اسم gluetun والمنفذ الذي يستمع عليه التطبيق. تصل حاوية reverse proxy إلى واجهة الويب الخاصة بـ qBittorrent عبر gluetun:8080. لا تحتاج إلى إدخال ports: لهذا الاتصال، لأن حركة المرور بين الحاويات تبقى على شبكة Docker ولا تمر عبر منفذ على المضيف.

للاتصال من الداخل إلى الخارج، استخدم اسم خدمة الحاوية الأخرى، مثل postgres:5432. يحل Gluetun أسماء الحاويات الأخرى من داخل مساحة الأسماء الخاصة به منذ الإصدار v3.41، لذلك ثبّت هذا الإصدار أو إصداراً أحدث إذا تعذر حل اسم ما.

يحدد جدار Gluetun الناري الجهات التي يمكنها فتح اتصال به. يُسمح بحركة المرور القادمة من شبكة Docker الخاصة بـ gluetun. أما العميل الموجود على شبكة فرعية مختلفة، مثل حاسوب محمول على شبكتك المحلية أو حاوية على شبكة bridge منفصلة، فتُسقط حركة مروره إلى أن تضيف تلك الشبكة الفرعية:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

المعنى الموثق دقيق: شبكات فرعية مفصولة بفواصل، يُسمح لـ Gluetun والحاويات التي تشاركه مكدس الشبكة بالوصول إليها.

الاتصالات الواردة من الإنترنت مشكلة منفصلة. يصل أقران عميل التورنت من جهة VPN، لذلك لا يفيد نشر المنفذ 6881 على المضيف. تحتاج إلى منفذ مُعاد توجيهه من مزود الخدمة، وإلى إدراج ذلك المنفذ في FIREWALL_VPN_INPUT_PORTS، الذي يسمح بالمنافذ القادمة من جهة خادم VPN. هذه هي النقطة التي تتركها معظم حزم الوسائط المبنية باستخدام Docker Compose معطلة.

مفتاح الإيقاف: ما الذي يحدث عند انقطاع النفق

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

يراقب Gluetun اتصاله بنفسه. يرسل كل دقيقة طلب صدى عبر ICMP (وهو ping) إلى العناوين الموجودة في HEALTH_ICMP_TARGET_IPS، والتي تكون افتراضياً 1.1.1.1,8.8.8.8. وكل خمس دقائق، ينشئ اتصالاً كاملاً عبر TCP وTLS (أمان طبقة النقل) إلى HEALTH_TARGET_ADDRESSES، وتكون القيمة الافتراضية cloudflare.com:443,github.com:443. عند فشل هذه الاختبارات، يعيد تشغيل VPN داخل الحاوي ويسجّل ذلك:

WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout

اقرأ سجلات الحاوي المتصل مع وضع هذا التسلسل في الاعتبار. إن الأسطر مثل connection refused وoperation not permitted وi/o timeout داخل التطبيق هي نتائج توقف النفق، وليست أسباباً له. توضح وثائق Gluetun ذلك صراحة، لأن المستخدمين يبلغون عن النتيجة ثم يقضون ساعات في البحث عنها.

يكون HEALTH_RESTART_VPN=on مفعّلاً افتراضياً، وينبغي أن يظل مفعّلاً. عطّله فقط أثناء تصحيح عطل محدد، لأن النفق المتوقف يظل متوقفاً عند تعطيله.

الترتيب: منع بدء المكدس قبل تشغيل النفق

تتضمن الصورة فحصاً لحالة Docker:

HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck

ينشئ هذا الأمر نسخة ثانية قصيرة العمر من gluetun، وتستعلم هذه النسخة عن خادم الحالة للنسخة قيد التشغيل على http://127.0.0.1:9999/. يجيب النفق العامل بـ 200 OK. أما النفق المعطّل فيجيب بـ 500 Internal server error مع سلسلة خطأ، وتُعلَّم الحاوية بأنها غير سليمة بعد فشل واحد.

ينتظر condition: service_healthy اكتمال ذلك. أما depends_on: [gluetun] العادي فلا ينتظر إلا بدء الحاوية، ويحدث ذلك قبل اكتمال المصافحة بعدة ثوانٍ. لذلك يبدأ التطبيق مع شبكة معطّلة، وغالباً ما يتوقف بعد فشل محاولة الاتصال الأولى. يشرح فحوصات الحالة في Docker Compose الصياغة وحقول التوقيت.

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

تحقّق من تسرّب DNS قبل الوثوق بالإعداد

يُعدّ DNS (نظام أسماء النطاقات) التسرّب الذي يستمر حتى عند إعداد النفق بشكل صحيح. يشغّل Gluetun محلّل أسماء خاصاً به داخل مساحة الأسماء، ويمرّر الاستعلامات عبر DoT (DNS over TLS) إلى Cloudflare افتراضياً: DNS_UPSTREAM_RESOLVER_TYPE=dot وDNS_UPSTREAM_RESOLVERS=cloudflare. اترك القيمتين كما هما، وستكون استعلاماتك مشفّرة وتمر عبر النفق.

الإعداد الذي يسبّب المشكلة هو DNS_UPSTREAM_PLAIN_ADDRESSES. يلجأ إليه المستخدمون عندما يفشل حل اسم، ويريدون أن يجيب الموجّه أو محلّل مزوّد الخدمة بدلاً من ذلك. توضّح وثائق Gluetun التكلفة صراحةً: لن تمرّ كل حركة DNS عبر نفق VPN، بل ستتسرّب خارجه. تبقى حركة الشبكة لديك خاصة، لكن قائمة أسماء المضيفين التي تزورها لا تبقى كذلك. يتناول DNS الذي يتوقف عن الحل عبر نفق WireGuard الخطأ نفسه في إصدار WireGuard.

لاختبار ذلك، اضبط HTTPPROXY=on على gluetun وانشر 8888:8888/tcp، ثم وجّه متصفحاً إلى ذلك الوكيل وافتح اختباراً لتسرّب DNS. يجب أن تُظهر النتيجة اسم مزوّد الخدمة أو Cloudflare، لا الموجّه المنزلي مطلقاً. تحذّر وثائق Gluetun نفسها من أن بعض اختبارات التسرّب تعرض نتائج غير معتادة، لأن محلّل الأسماء داخل مساحة الأسماء هو وسيط تخزين مؤقت محلي، وليس الخادم الذي يجيب في النهاية. اعتبر ظهور بلد غير صحيح أو محلّل أسماء مزوّد خدمة الإنترنت لديك الإشارة الفعلية إلى وجود التسرّب.

إضافة Tailscale بجوار حاوية VPN الجانبية، وأيهما تكون له الأولوية

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

لذلك تعتمد الإجابة على إعداد واحد.

  • Tailscale في حاويته الخاصة، بالإعداد الافتراضي: لا يرى مطلقاً حركة المرور الصادرة من التطبيق. يتولى Gluetun كل هذه الحركة. يصل Tailscale إلى التطبيق عبر gluetun:8080، تماماً مثل أي حاوية خارجية أخرى.
  • Tailscale متصل بمساحة أسماء Gluetun مع network_mode: "service:gluetun": يحتاج إلى cap_add الخاص به من net_admin وnet_raw، لأن الصلاحيات لا تنتقل مع مساحة الأسماء. في وضع شبكات مساحة المستخدم الافتراضي، يكون TS_USERSPACE مفعّلاً، ولا ينشئ tailscaled أي واجهة على الإطلاق، بل يعمل كوكيل SOCKS5 أو HTTP، ولذلك لا يستطيع تغيير التوجيه. يواصل Gluetun حمل كل حركة المرور.
  • السلوك نفسه مع TS_USERSPACE=false: ينشئ tailscaled جهاز نفق ويثبّت المسارات، لكن لنطاق tailnet 100.64.0.0/10 فقط، إضافة إلى أي مسارات شبكات فرعية تعلنها باستخدام TS_ROUTES. تظل حركة المرور العامة تخرج عبر Gluetun.
  • أي من الحالات السابقة مع تحديد عقدة خروج، sudo tailscale set --exit-node=<exit-node-ip>: يستولي Tailscale على المسار الافتراضي وتكون له الأولوية. لا تجمع هذا مع Gluetun. مسار افتراضي واحد، ومالك واحد.

يظهر أحد الآثار الجانبية عندما يعمل Tailscale داخل النفق. يرى أقرانه عنوان مزوّد VPN، لذلك توقّع استخدام المرحّلات بوتيرة أكبر. يعرض tailscale status القيمة relay "..." بجوار أحد الأقران بدلاً من direct عند حدوث ذلك. يعمل الاتصال، لكنه يكون أبطأ. إذا كانت الشبكة التراكبية هي كل ما تحتاج إليه فعلياً، فابدأ بـالفرق بين WireGuard العادي وTailscale.

ما الذي يتعطل، والرسالة التي ستظهر

يرفض Docker إنشاء حاوية التطبيق. يعني Error response from daemon: conflicting options: port publishing and the container type network mode أن كتلة ports: ما زالت مرتبطة بالخدمة المرفقة. انقلها إلى gluetun.

يرفض Compose الملف بأكمله. لا يمكن للخدمة ضبط كل من network_mode وnetworks. ضع الشبكات في gluetun.

لا تستطيع حاوية أخرى حل اسم التطبيق. يُعد curl: (6) Could not resolve host: qbittorrent سلوكاً صحيحاً، لأن الحاوية المرفقة لم تنضم إلى أي شبكة ولم تسجّل أي اسم. استخدم gluetun والمنفذ.

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

لم يعد للتطبيق اتصال بالشبكة بعد تعديل gluetun. يؤدي إعادة تشغيل gluetun أو إعادة إنشائه إلى قطع الاتصال عن كل ما هو متصل به. أعد تشغيل تلك الحاويات.

تُحمّل الصفحات الصغيرة، بينما تتوقف الصفحات الكبيرة. يتعلق ذلك بـMTU (وحدة الإرسال القصوى). يضيف النفق حمولة إضافية، ويسقط شيء ما في المسار الحزم الأكبر من دون إرسال خطأ إلى المصدر. خفّض WIREGUARD_MTU، وجرّب 1400، ثم 1320.

لا تصبح حالة Gluetun صحية أبداً. يحدّد فحص بدء التشغيل أولى نقاط الاشتباه: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. تحقق مما إذا كان المفتاح منتهياً، ثم مما إذا كانت قائمة الخوادم قديمة، ثم مما إذا كان جدار الحماية على المضيف يحظر UDP الصادر.

FAQ

لماذا توقفت المنافذ المنشورة لحاويتي عن العمل خلف Gluetun؟

لأن network_mode: "service:gluetun" يضع الحاوية داخل مساحة أسماء الشبكة الخاصة بـgluetun، ولكل مساحة أسماء عنوان IP واحد ومجموعة واحدة من المنافذ التي تستمع إليها. يظل التطبيق يستمع، لكن يجب أن تكون قاعدة النشر في الحاوية التي تملك مساحة الأسماء. انقل قائمة ports: إلى خدمة gluetun. إذا أبقيتها في الخدمة المرتبطة، فلن ينشئها Docker أصلاً: Error response from daemon: conflicting options: port publishing and the container type network mode.

كيف أصل إلى حاوية داخل نفق VPN من حاوية خارجه؟

استخدم اسم خدمة gluetun والمنفذ الذي يستمع إليه التطبيق، مثل gluetun:8080. لا تنتمي الحاوية المرتبطة إلى أي شبكة Docker خاصة بها، لذلك لا يُحل اسمها. لا تحتاج حركة المرور بين الحاويات إلى نشر أي منفذ. في الاتجاه الآخر، تصل حاوية داخل مساحة الأسماء إلى حاوية خارجها باستخدام اسم خدمتها، مثل postgres:5432، في Gluetun v3.41 والإصدارات الأحدث. يُسقط gluetun اتصال عميل في شبكة فرعية مختلفة، مثل حاسوب محمول على شبكتك المحلية، بواسطة جدار الحماية إلى أن تضيف تلك الشبكة الفرعية إلى FIREWALL_OUTBOUND_SUBNETS.

هل يعمل Gluetun كمفتاح إيقاف عند انقطاع VPN؟

نعم، لسببين في الوقت نفسه. لا تملك الحاوية المرتبطة أي مسار سوى المسار الموجود في مساحة الأسماء المشتركة، لذلك يؤدي توقف النفق إلى تركها بلا مسار للخروج من الجهاز. كما يسمح جدار حماية Gluetun بحركة المرور الصادرة عبر النفق وإلى نقطة نهاية خادم VPN فقط. ثم يعيد Gluetun تشغيل VPN داخلياً، ويسجل WARN [vpn] restarting VPN because it failed to pass the healthcheck، بدلاً من الخروج، لأن كل حاوية مرتبطة تفقد شبكتها عند إعادة تشغيل gluetun نفسه.

Tailscale وGluetun في المكدس نفسه: أيهما يمرر حركة المرور الصادرة؟

Gluetun، في جميع الإعدادات باستثناء إعداد واحد. يوجّه Tailscale حركة المرور بين الأجهزة في tailnet الخاص بك افتراضياً فقط، ويترك حركة المرور العامة كما هي. في وضع userspace الافتراضي لصورة الحاوية، لا ينشئ أي واجهة، لذلك لا يمكنه التأثير في التوجيه. عند استخدام TS_USERSPACE=false، يثبّت مسارات إلى 100.64.0.0/10 وشبكاتك الفرعية المعلنة فقط. الاستثناء هو exit node: يجعل sudo tailscale set --exit-node=<exit-node-ip> Tailscale هو المسار الافتراضي، وعندها تكون له الأولوية. اختر منتجاً واحداً لامتلاك المسار الافتراضي بدلاً من تكديس المنتجين.