الأدلة التقنية

تثبيت Traefik على خادمك: Reverse Proxy لحاويات Docker بشهادات TLS تلقائية

مع Traefik، يحصل كل تطبيق على نطاقه وشهادة HTTPS بمجرد إضافة بضعة أسطر إلى ملف Compose الخاص به. نثبته على Ubuntu 24.04، ونحمي لوحة التحكم، ونجهز النسخ الاحتياطي والتحديث.

تثبيت Traefik على خادمك: Reverse Proxy لحاويات Docker بشهادات TLS تلقائية

عندما تضيف تطبيقاً جديداً إلى السيرفر Server فهو يحتاج إلى ثلاثة أمور حتى يصل إليه المستخدم: نطاق Domain يكتبه في المتصفح، وشهادة TLS Certificate تحمي الاتصال، وقاعدة في ال Reverse Proxy توجه الطلبات إلى ال Container الصحيح، والمشكلة الحقيقية في هذه القاعدة الأخيرة، فإذا كتبتها يدوياً في ملف إعداد بعيد عن التطبيق فسوف تنسى تعديلها يوماً ما عندما يتغير اسم ال Container أو منفذه، أو تبقى القاعدة في مكانها بعد أن تحذف التطبيق نفسه بشهور ولا يتذكر أحد لماذا هي موجودة.

ولهذه المهمة أكثر من أداة، فمع Nginx Proxy Manager تدير كل نطاق من واجهة ويب Web UI ويحفظ الإعداد في قاعدة بياناته، أما Traefik Proxy فيقرأ الإعداد من التطبيقات نفسها، وتجد الأدوات الثلاث جنباً إلى جنب في مقارنة Nginx Proxy Manager وTraefik وHAProxy، وهذا الدليل عن Traefik.

وTraefik هو Reverse Proxy مفتوح المصدر Open Source يراقب Docker باستمرار، فعندما يبدأ Container يحمل وسوماً Labels معينة ينشئ له Traefik قاعدة توجيه Router ويطلب له شهادة من Let's Encrypt، وعندما يتوقف ال Container يحذف القاعدة، ويسمى هذا السلوك الاكتشاف التلقائي للخدمات Service Discovery. ولاحظ أنه لا توجد هنا واجهة لتعديل الإعداد، وإنما تكتبه في الوسوم وفي ملفات نصية تحفظها في Git وتراجعها كأي كود Code، لذلك فهو مناسب للفريق الذي ينشر تطبيقاته بملفات Compose ويريد إعداد التوجيه Routing بجانبها، أما إذا كنت تفضل الإعداد بالنقر فابق مع Nginx Proxy Manager.

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

  • كيف يعمل Traefik، والفرق بين الإعداد الثابت والإعداد الديناميكي.
  • تثبيت Traefik بملف Compose مع تحويل HTTP إلى HTTPS وشهادات Let's Encrypt التلقائية وترويسات الأمان.
  • نشر تطبيق عبر الوسوم، واستخدام ال Middlewares لتعديل الطلب قبل وصوله إلى التطبيق.
  • تأمين لوحة التحكم Dashboard، وحماية ال Docker socket بوسيط Socket Proxy.
  • التحقق من النجاح، والنسخ الاحتياطي، والتحديث، وأشهر المشكلات وحلولها.
⚠️
لا يستمع على المنفذين 80 و443 في السيرفر الواحد إلا برنامج واحد، فإذا كان Nginx Proxy Manager أو Nginx أو Apache يعمل على السيرفر نفسه فلن يبدأ Traefik، وعليك أن تختار أحدهما وتنقل إليه جميع النطاقات، ولا تشغل الاثنين معاً إلا إذا وضعت أحدهما خلف الآخر عن قصد.
💡
منصتا Coolify وDokploy تأتيان ومعهما Traefik مدمجاً تديرانه بنفسيهما، فإذا كنت تنوي تثبيت إحداهما على هذا السيرفر فلا تثبت Traefik منفصلاً، واتبع دليل تثبيت Coolify أو تثبيت Dokploy مباشرة، وسوف يفيدك هذا الدليل حينئذ في فهم ما تفعله المنصة خلف الواجهة.

المتطلبات Requirements

  • سيرفر يعمل بنظام Ubuntu 24.04 وعليه Docker Engine وCompose v2، وإذا لم يكن جاهزاً فاتبع دليل تثبيت Docker على Ubuntu، ثم دليل تأمين خادم VPS من أول دخول.
  • نطاق تدير سجلات DNS Records الخاصة به، وفيه سجل A لكل اسم فرعي Subdomain يشير إلى العنوان العام Public IP للسيرفر 203.0.113.10، وسوف نستخدم في هذا الدليل app.example.com للتطبيق وtraefik.example.com للوحة التحكم، وإذا كانت هذه السجلات جديدة عليك فقد شرحناها في شرح DNS وسجلاته للمبتدئين.
  • المنفذان Ports 80/tcp و443/tcp مفتوحان من الإنترنت إلى السيرفر، في جدار الحماية Firewall لدى المزود وعلى السيرفر معاً، ولاحظ أن المنفذ 80 ضروري للتحقق من ملكية النطاق عبر HTTP (HTTP-01 Challenge) حتى لو كانت خدماتك كلها عبر HTTPS.
  • لا يستمع أي برنامج آخر على المنفذين، وتحقق من ذلك بالأمر التالي، فإذا لم يطبع شيئاً فالمنفذان متاحان:
sudo ss -ltnp | grep -E ':(80|443) '

أما عن الموارد Resources فإن Traefik برنامج واحد مكتوب بلغة Go ويكفيه أصغر خادم افتراضي خاص VPS، واستهلاكه يزيد مع عدد الطلبات أكثر مما يزيد مع عدد التطبيقات التي يخدمها.

كيف يعمل Traefik؟ الإعداد الثابت والإعداد الديناميكي

يصل الطلب إلى Traefik على نقطة دخول EntryPoint، أي منفذ يستمع عليه مثل 80 أو 443، فيطابقه Traefik مع قاعدة Router مثل اسم النطاق app.example.com، ثم يمر الطلب على Middleware واحد أو أكثر يعدل فيه، كأن يضيف ترويسات Headers أو يطلب كلمة مرور، وفي النهاية يصل إلى ال Service، أي ال Container الذي يخدم هذا الطلب.

وللإعداد Configuration في Traefik قسمان كما يشرح توثيق الإعداد الرسمي، والجدول التالي يبين الفرق بينهما:

القسمما يحددهمصدرهمتى يطبق
الإعداد الثابت Static Configurationنقاط الدخول، ومصادر الإعداد Providers، وحساب Let's Encrypt، ولوحة التحكم، والسجلات Logsالملف traefik.yml أو خيارات سطر الأوامر CLI Flagsعند الإقلاع فقط، وأي تغيير فيه يحتاج إلى إعادة تشغيل Traefik
الإعداد الديناميكي Dynamic Configurationال Routers وال Services وال Middlewaresوسوم Docker، أو ملفات يقرؤها File Providerفوراً ودون إعادة تشغيل

وسوف نكتب الإعداد الثابت في الملف traefik.yml وليس في خيارات سطر الأوامر، والسبب أن الملف أسهل في القراءة والمراجعة من سطر طويل من الخيارات، أما الإعداد الديناميكي فنكتبه في وسوم كل تطبيق، ومعه مجلد صغير لل Middlewares المشتركة بين التطبيقات.

التثبيت Installation

نبدأ بشبكة Docker مشتركة

سوف نضع Traefik وكل تطبيق يخدمه على شبكة Docker Docker Network واحدة اسمها proxy، وبهذا الشكل يصل Traefik إلى كل Container مباشرة، ولا تحتاج التطبيقات إلى نشر أي منفذ على السيرفر. وإذا أردت أن تفهم لماذا نفعل ذلك وما أنواع الشبكات في Docker، ولماذا نضع قاعدة البيانات على شبكة داخلية خاصة بها، فقد شرحنا ذلك بالتفصيل في دليل شبكات Docker وأفضل الممارسات. أنشئ الشبكة مرة واحدة كما يلي:

docker network create proxy

أين نحفظ الإعداد والشهادات؟

sudo mkdir -p /opt/traefik/dynamic /opt/traefik/letsencrypt
sudo chown -R $USER: /opt/traefik
cd /opt/traefik
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

يحفظ Traefik في الملف acme.json حساب Let's Encrypt والشهادات ومفاتيحها الخاصة Private Keys، ولهذا يرفض استخدام الملف إذا كانت صلاحياته Permissions أوسع من 600، ويكتب في السجل رسالة مثل permissions 644 for /letsencrypt/acme.json are too open, please use 600.

نكتب الإعداد الثابت في traefik.yml

الآن سوف نكتب الإعداد الثابت في الملف /opt/traefik/traefik.yml كما يلي:

global:
  checkNewVersion: false
  sendAnonymousUsage: false

log:
  level: INFO

accessLog: {}

api:
  dashboard: true

ping: {}

entryPoints:
  web:
    address: ":80"
    http:
      aliasHeadersStrategy: delete
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      aliasHeadersStrategy: delete
      tls:
        certResolver: letsencrypt

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false
    network: proxy
  file:
    directory: /etc/traefik/dynamic
    watch: true

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

  • تستقبل نقطة الدخول web المنفذ 80، وتحول كل طلب HTTP إلى HTTPS تحويلاً دائماً Permanent Redirect. وقد يتساءل البعض: إذا كان كل طلب على المنفذ 80 يتحول إلى HTTPS، فكيف تصل Let's Encrypt إلى ملف التحقق Challenge File عبر HTTP؟ والإجابة أن Traefik يعالج طلبات /.well-known/acme-challenge/ بمسار داخلي له أعلى أولوية Priority ممكنة، فيصل طلب التحقق إليه قبل أن يمر على قاعدة التحويل.
  • تستقبل websecure المنفذ 443، ومعنى certResolver: letsencrypt أن كل Router عليها يحصل على شهادة تلقائياً، فلا داعي لتكرار ذلك في وسوم كل تطبيق.
  • الخيار aliasHeadersStrategy: delete يحذف كل ترويسة في اسمها حرف غير الحروف والأرقام والشرطة، مثل الشرطة السفلية أو النقطة في X_Auth_User، والسبب أن تطبيقات PHP وغيرها تقرأ هذا الاسم على أنه X-Auth-User، وبالتالي يستطيع المهاجم أن ينتحل ترويسة يثق بها التطبيق. وقد أضيف هذا الخيار في الإصدار v3.7.12 بديلاً عن الخيار الأقدم underscoreHeadersStrategy، ويحذرك Traefik في السجل عند الإقلاع عن كل نقطة دخول تركته فيها على قيمته الافتراضية keep.
  • القسم certificatesResolvers يعرف حساب Let's Encrypt باسم letsencrypt، ويستخدم التحقق عبر HTTP (HTTP-01) على نقطة الدخول web، ويجدد Traefik كل شهادة تلقائياً قبل انتهائها بثلاثين يوماً، فالشهادة صالحة 90 يوماً ويبدأ التجديد في يومها الستين.
  • مع exposedByDefault: false في Docker Provider لا ينشر Traefik إلا ال Container الذي يحمل الوسم traefik.enable=true، والسبب أن القيمة الافتراضية true تنشر كل Container على السيرفر، ومنها قواعد البيانات Databases والأدوات الداخلية، وهذا بكل تأكيد ما لا تريده.
  • يحدد network: proxy الشبكة التي يصل منها Traefik إلى ال Containers، ويهمك هذا عندما يكون التطبيق على أكثر من شبكة.
  • يقرأ File Provider كل ملف في المجلد /etc/traefik/dynamic ويطبق أي تعديل فيه فوراً.
  • يفعل ping نقطة الفحص /ping التي يستخدمها فحص الصحة Healthcheck داخل ال Container، ولا ننشر منفذها على السيرفر.
📌
في 3 فبراير 2020 توقفت خدمة Microsoft Teams قرابة ثلاث ساعات، وكان المستخدمون يرون رسالة تقول إن التطبيق فشل في إنشاء اتصال HTTPS مع سيرفرات Microsoft، ثم أعلنت Microsoft أن السبب شهادة مصادقة انتهت صلاحيتها ولم يجددها أحد في وقتها. والدرس أن تجديد الشهادات يدوياً سوف يفشل يوماً ما مهما كان الفريق كبيراً، لذلك اترك التجديد ل Traefik، وراقب تاريخ الانتهاء بالأمر الموجود في قسم التحقق أدناه.

ترويسات الأمان المشتركة بين كل التطبيقات

في الملف /opt/traefik/dynamic/middlewares.yml سوف نعرف Middleware اسمه security-headers يضيف ترويسات الأمان Security Headers إلى كل استجابة، ثم نربطه بالتطبيقات لاحقاً:

http:
  middlewares:
    security-headers:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        contentTypeNosniff: true
        frameDeny: true
        referrerPolicy: "strict-origin-when-cross-origin"

يطلب stsSeconds من المتصفح Browser ألا يستخدم HTTP مع النطاق مدة سنة، وهذا ما يسمى HSTS، ويمنع contentTypeNosniff المتصفح من تخمين نوع الملف، ويمنع frameDeny عرض الصفحة داخل إطار iframe في موقع آخر، ويقلل referrerPolicy ما يرسله المتصفح من روابط صفحاتك إلى المواقع الأخرى، وتجد شرح كل ترويسة وكيف تضيف CSP دون أن تكسر تطبيقك في دليل ترويسات الأمان بالتفصيل: HSTS وCSP.

⚠️
يصعب التراجع عن HSTS بعد أن يحفظه المتصفح، ومع stsIncludeSubdomains يشمل كل اسم فرعي تحت النطاق، لذلك ابدأ بقيمة صغيرة مثل 300 ثانية، ثم ارفعها إلى سنة بعد أن تتأكد أن الشهادات تصدر وتتجدد لكل الأسماء، وإذا كان أحد التطبيقات يحتاج إلى العرض داخل إطار فعرف له Middleware آخر دون frameDeny.

كيف تولد كلمة مرور لوحة التحكم؟

سوف نحمي لوحة التحكم بمصادقة HTTP الأساسية Basic Authentication عبر BasicAuth Middleware، وهذا ال Middleware يقبل كلمة المرور بصيغة هاش Hash مثل BCrypt، وتولدها الأداة htpasswd كما يلي:

sudo apt install -y apache2-utils
htpasswd -nB admin

تطلب الأداة كلمة المرور مرتين ثم تطبع سطراً مثل admin:$2y$05$...، فانسخ السطر كاملاً إلى الملف /opt/traefik/.env بين علامتي تنصيص مفردتين:

TRAEFIK_DASHBOARD_USERS='admin:$2y$05$Jm0mK1x5Q8bW0eQnI3zHVe4mR2sL7kT9pYcA1dF6gH8jN0uV3wXyZ'
chmod 600 /opt/traefik/.env

ولاحظ أن الهاش يحتوي على العلامة $، وهي علامة المتغيرات في Compose، ولكن Compose يقرأ القيمة حرفياً إذا وضعتها بين علامتي تنصيص مفردتين في ملف .env، أما إذا كتبت الهاش داخل compose.yaml مباشرة فضاعف كل علامة $ لتصبح $$.

نجمع كل شيء في ملف Compose

الآن نكتب الملف /opt/traefik/compose.yaml، وفيه الإصدار v3.7.13 مثبتاً برقمه الكامل:

services:
  traefik:
    image: traefik:v3.7.13
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
    healthcheck:
      test: ["CMD", "traefik", "healthcheck", "--ping"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
      start_interval: 5s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth,security-headers@file"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_USERS}"

networks:
  proxy:
    external: true

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

  • يبحث Traefik تلقائياً عن إعداده الثابت في /etc/traefik/traefik.yml، فلا نحتاج إلى أي خيار في command.
  • يمنع no-new-privileges أي عملية داخل ال Container من رفع صلاحياتها، وتستخدمه أمثلة Traefik الرسمية أيضاً.
  • إذا كان لل Container فحص صحة فلا يوجه Traefik إليه الطلبات قبل أن تصبح حالته healthy، وهذا يشمل Traefik نفسه ولوحة التحكم، لذلك أضفنا start_interval ليجري الفحص كل 5 ثوان في البداية، فتظهر اللوحة خلال ثوان وليس بعد نصف دقيقة.
  • تنشئ الوسوم Router اسمه dashboard يوجه النطاق traefik.example.com إلى الخدمة الداخلية api@internal، ويمر الطلب أولاً على كلمة المرور ثم على ترويسات الأمان، والتفاصيل في قسم لوحة التحكم أدناه.

شغل الخدمة وانتظر حتى تصبح حالتها healthy:

cd /opt/traefik
docker compose up -d
docker compose ps
docker compose logs --tail 30 traefik

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

NAME      IMAGE             STATUS
traefik   traefik:v3.7.13   Up 20 seconds (healthy)

وعند قراءة السجل سوف تجد غالباً تحذيرين بمستوى WRN عند الإقلاع، الأول عن رفض بعض الحروف المرمزة Encoded Characters في مسار الطلب، وهو تنبيه عام يظهر لكل من يستخدم القيم الافتراضية، والثاني يقول إن aliasHeadersStrategy غير مضبوط على نقطة الدخول traefik، وهي نقطة دخول داخلية على المنفذ 8080 ينشئها Traefik ل ping ولوحة التحكم ولا ننشرها على السيرفر، لذلك لا يؤثر عليك هذا التحذير، وإذا أردت إسكاته فعرف نقطة الدخول traefik بنفسك في entryPoints وأضف إليها الخيار نفسه.

💡
إذا كانت هذه أول مرة تجرب فيها الإعداد، فأضف إلى acme في traefik.yml السطر caServer: https://acme-staging-v02.api.letsencrypt.org/directory، فيستخدم Traefik بيئة الاختبار Staging في Let's Encrypt، وحدودها Rate Limits أوسع بكثير ولكن شهاداتها غير موثوقة في المتصفح، وبعد نجاح التجربة احذف السطر وأفرغ acme.json بالأمر : > letsencrypt/acme.json، ثم أعد تشغيل Traefik ليطلب شهادات حقيقية.

كيف تنشر تطبيقاً عبر الوسوم؟

لنأخذ مثالاً بالتطبيق الصغير whoami، وهو تطبيق يعيد تفاصيل الطلب كما وصلته، فترى الترويسات التي أضافها Traefik، وهذا هو الملف /opt/whoami/compose.yaml، ولاحظ أنه لا ينشر أي منفذ على السيرفر:

services:
  whoami:
    image: traefik/whoami:v1.12.0
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`app.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.middlewares=security-headers@file"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

networks:
  proxy:
    external: true
sudo mkdir -p /opt/whoami
sudo chown $USER: /opt/whoami
cd /opt/whoami
docker compose up -d

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

  • يحدد rule متى يطابق الطلب هذا ال Router، وتجد بقية الصيغ مثل PathPrefix في صفحة القواعد، واكتب اسم النطاق بين علامتي ` وليس بين علامتي تنصيص.
  • loadbalancer.server.port هو المنفذ داخل ال Container وليس منفذاً على السيرفر، وإذا أعلن ال Image عن منفذ واحد بالتعليمة EXPOSE فسوف يكتشفه Traefik وحده، ولكن كتابته صراحة تحميك من الخطأ عندما يعلن ال Image عن أكثر من منفذ.
  • يشير security-headers@file إلى ال Middleware الذي عرفناه في الملف، أما ال Middleware المعرف في وسوم Docker فلاحقته @docker، وإذا كان ال Middleware في المصدر نفسه فلك أن تحذف اللاحقة كما فعلنا مع dashboard-auth.

بعد ثوان من تشغيل ال Container ينشئ Traefik ال Router ويطلب الشهادة، ولا تحتاج إلى انتظار انتشار DNS Propagation لكي تختبر، وإنما وجه النطاق إلى العنوان المحلي من السيرفر نفسه:

curl -s -D - --resolve app.example.com:443:127.0.0.1 https://app.example.com/

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

HTTP/1.1 200 OK
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
...
Hostname: 3f2a9c1b7d4e
Host: app.example.com
X-Forwarded-For: 203.0.113.50
X-Forwarded-Proto: https
X-Real-Ip: 203.0.113.50

وإذا لم تصدر الشهادة بعد فسوف يرفض curl الاتصال، والسبب أن Traefik يقدم في هذه الحالة شهادته الافتراضية الموقعة ذاتياً Self-signed باسم TRAEFIK DEFAULT CERT، فأضف الخيار -k للتجربة فقط، وراجع قسم الشهادات في المشكلات الشائعة أدناه.

كيف تعدل الطلب قبل وصوله إلى التطبيق بال Middlewares؟

يقع ال Middleware في الطريق بين ال Router وال Service، ولك أن تربط أكثر من Middleware بال Router الواحد مفصولة بفاصلة، فتنفذ بالترتيب الذي كتبتها به. لنفرض أن لديك أداة داخلية تريد قصرها على شبكة المكتب أو الشبكة الخاصة الافتراضية VPN، فالحل أن تضيف إليها IPAllowList كما يلي:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.tools.rule=Host(`tools.example.com`)"
      - "traefik.http.routers.tools.entrypoints=websecure"
      - "traefik.http.routers.tools.middlewares=office-only,security-headers@file"
      - "traefik.http.middlewares.office-only.ipallowlist.sourcerange=203.0.113.0/24,10.8.0.0/24"

وأي عنوان خارج هذه النطاقات سوف يتلقى الرمز 403، أما ال Middlewares التي تستخدمها أكثر من خدمة فضعها في مجلد dynamic كما فعلنا مع ترويسات الأمان، حتى تعرفها مرة واحدة وتعدلها في مكان واحد.

كيف تؤمن لوحة التحكم؟

تعرض لوحة تحكم Traefik كل ال Routers وال Services وال Middlewares وحالتها، وهي مفيدة جداً في تتبع الأخطاء، ولكنها تكشف أيضاً كل نطاقاتك وبنية خدماتك لمن يصل إليها.

والحل السريع الذي تجده في كثير من الأمثلة هو الخيار api.insecure، وهذا الحل غير مناسب إطلاقاً لسيرفر على الإنترنت، والسبب أنه يعرض اللوحة وال API على المنفذ 8080 دون أي مصادقة Authentication، فإذا نشرت هذا المنفذ أصبح كل ما في اللوحة متاحاً لأي شخص. لذلك لا نستخدمه، وإنما نضع اللوحة خلف Router له ثلاثة شروط:

  • نطاق خاص بها هو traefik.example.com، عبر HTTPS فقط.
  • كلمة مرور عبر dashboard-auth الذي يقرأ الهاش من .env.
  • الخدمة api@internal، وهي الخدمة الداخلية التي تقدم اللوحة وال API معاً.

افتح https://traefik.example.com/dashboard/ مع الشرطة المائلة في آخره، وأدخل اسم المستخدم وكلمة المرور، وإذا فتحت العنوان الرئيسي للنطاق Root URL فسوف يحولك Traefik إلى /dashboard/ تلقائياً.

وإذا لم ترد أن تصل إلى اللوحة من الإنترنت أصلاً فأمامك خياران، الأول أن تضيف إليها Middleware من نوع IPAllowList كما في القسم السابق، فلا يفتحها إلا من على شبكة الفريق أو ال VPN، والثاني أن تحذف وسومها كلها وتجعل dashboard: false في traefik.yml، وتكتفي بالسجلات وبالأمر docker compose logs.

⚠️
يرسل Basic Authentication كلمة المرور مع كل طلب، لذلك لا يصلح إلا عبر HTTPS، وهو حماية مقبولة للوحة يدخلها شخص أو اثنان، ولكنه لا يوفر تحققاً بخطوتين Two-Factor Authentication ولا سجلاً للدخول.

لماذا يحتاج ال Docker socket إلى حماية خاصة؟

يقرأ Traefik الوسوم من ال API الخاص بـ Docker عبر الملف /var/run/docker.sock، ومن يصل إلى هذا الملف يستطيع إنشاء Container بصلاحيات root على السيرفر كله كما ينبه توثيق أمان Docker، وTraefik هو البرنامج المعرض للإنترنت مباشرة، فإذا استغل مهاجم ثغرة Vulnerability فيه فقد يصل عبر ال socket إلى السيرفر بأكمله.

وقد يبدو أن اللاحقة :ro في ملف Compose تحل المشكلة، ولكن في الواقع فهي غير كذلك، فهي تمنع تعديل الملف نفسه داخل ال Container ولا تمنع إرسال أوامر إلى Docker عبره، وبالتالي فهي خطوة صغيرة وليست حماية كاملة. والحل الأنسب كما يوصي توثيق Traefik هو أن تضع بينهما Socket Proxy مثل docker-socket-proxy، وهو وسيط يمرر طلبات القراءة التي يحتاج إليها Traefik فقط ويرفض غيرها بالرمز 403.

أضف الخدمة التالية إلى /opt/traefik/compose.yaml، ثم احذف سطر docker.sock من خدمة traefik وأضف إليها الشبكة socket:

services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy:v0.5.0
    container_name: socket-proxy
    restart: unless-stopped
    environment:
      CONTAINERS: 1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - socket

  traefik:
    # ... الإعداد السابق نفسه دون سطر docker.sock
    networks:
      - proxy
      - socket

networks:
  proxy:
    external: true
  socket:
    internal: true

بعد ذلك وجه Docker Provider إلى ال Socket Proxy في traefik.yml، وأعد إنشاء الخدمتين بالأمر docker compose up -d:

providers:
  docker:
    endpoint: "tcp://socket-proxy:2375"
    exposedByDefault: false
    network: proxy

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

  • يسمح CONTAINERS: 1 بقراءة قائمة ال Containers ووسومها، أما الأحداث Events وPING وVERSION فمسموحة افتراضياً، وطلبات التعديل POST ممنوعة افتراضياً، فلو حاول أحد إيقاف Container عبر ال Socket Proxy لتلقى الرمز 403.
  • يعزل internal: true الشبكة socket عن الخارج، فلا يصل إلى ال Socket Proxy إلا Traefik.

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

  • يعرض الأمر docker compose ps في /opt/traefik ال Container traefik بحالة healthy.
  • يعيد طلب HTTP تحويلاً دائماً إلى HTTPS:
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://app.example.com/
301 https://app.example.com/
  • يعيد الأمر curl -s -o /dev/null -w '%{http_code}\n' https://app.example.com/ الرمز 200 دون الخيار -k، وهذا يعني أن الشهادة موثوقة.
  • يعرض الأمر التالي جهة الإصدار Issuer وهي Let's Encrypt، وتاريخ انتهاء بعد نحو 90 يوماً:
echo | openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null | openssl x509 -noout -issuer -dates
  • تعيد لوحة التحكم الرمز 401 دون كلمة المرور والرمز 200 معها، ويعرض ال API ال Routers النشطة وإصدار Traefik:
curl -s -u admin https://traefik.example.com/api/version
curl -s -u admin https://traefik.example.com/api/http/routers

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

{"Version":"3.7.13","Codename":"langres",...}
  • أي نطاق لا يطابق Router يعيد الرسالة 404 page not found، ولا يظهر في /api/http/routers أي Container لم تضف إليه traefik.enable=true.

النسخ الاحتياطي Backup والاستعادة Restore

لا توجد قاعدة بيانات ل Traefik، فحالته كلها ملفات في /opt/traefik: الإعداد الثابت، ومجلد dynamic، وملف Compose، والملف .env، والملف acme.json، أما توجيه كل تطبيق فمحفوظ في ملف Compose الخاص به، وبالتالي يدخل في النسخة الاحتياطية لذلك التطبيق.

انسخ المجلد دون أن توقف Traefik، والسبب أن الملف acme.json لا يتغير إلا عند إصدار شهادة أو تجديدها:

sudo tar czf /root/traefik-$(date +%F).tar.gz -C /opt traefik

وللاستعادة على سيرفر جديد ثبت Docker، وأنشئ الشبكة، وفك النسخة، وتأكد من صلاحيات acme.json، ثم شغل الخدمة:

docker network create proxy
sudo tar xzf /root/traefik-2026-10-02.tar.gz -C /opt
sudo chown -R $USER: /opt/traefik
chmod 600 /opt/traefik/letsencrypt/acme.json /opt/traefik/.env
cd /opt/traefik
docker compose up -d

ومع وجود acme.json يستخدم Traefik الشهادات الحالية مباشرة ولا يطلب من Let's Encrypt شهادات جديدة لكل النطاقات دفعة واحدة، وهذا يهمك إذا كانت نطاقاتك كثيرة حتى لا تصطدم بحدود الإصدار، وإذا نقلت التطبيقات إلى السيرفر الجديد أيضاً فانقلها مع ملفات Compose الخاصة بها، ثم غير سجلات DNS.

🛑
تحتوي النسخة على المفاتيح الخاصة لكل شهاداتك ومفتاح حساب Let's Encrypt وهاش كلمة مرور اللوحة، لذلك خزنها مشفرة Encrypted خارج السيرفر، ولا تضعها في مستودع Git عام مع بقية ملفات الإعداد.

التحديث Upgrade إلى إصدار أحدث

  1. راجع صفحة الإصدارات، واقرأ قسم الإصدار الجديد في ملاحظات الترحيل Migration ضمن السلسلة 3، والسبب أن الإصدارات التصحيحية تغير أحياناً سلوكاً افتراضياً لأسباب أمنية، كما فعل الإصدار v3.7.13 مع بعض الترويسات وصيغ الطلبات.
  2. أنشئ نسخة احتياطية كما في القسم السابق.
  3. غير الوسم Tag في compose.yaml إلى الإصدار الجديد برقمه الكامل، وليس latest ولا v3، ثم نفذ:
cd /opt/traefik
docker compose pull
docker compose up -d
docker compose logs --tail 30 traefik

يتوقف استقبال الطلبات بضع ثوان أثناء إعادة إنشاء ال Container، فاختر وقتاً قليل الحركة، وبعد ذلك افتح اللوحة وتأكد أن كل ال Routers خضراء، وابحث في السجل عن WRN وERR لأن Traefik ينبهك فيه إلى الخيارات المهملة Deprecated، وإذا ظهرت مشكلة فأعد الوسم القديم ونفذ docker compose up -d.

أما الانتقال من السلسلة 2 إلى 3 فهو ترحيل كبير يغير صيغة بعض القواعد والخيارات، ولا يكفي فيه تغيير رقم الإصدار، لذلك اتبع فيه دليل الترحيل من v2 إلى v3 خطوة بخطوة.

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

يعيد Traefik الرسالة 404 page not found

معنى هذه الرسالة أن الطلب وصل إلى Traefik ولكن لم يطابقه أي Router، فافتح /api/http/routers أو اللوحة وتحقق مما يلي:

  • أن ال Container يحمل traefik.enable=true، فبسبب exposedByDefault: false يتجاهل Traefik ما عداه.
  • أن النطاق في Host() يطابق النطاق الذي تطلبه حرفاً بحرف، وأنه بين علامتي `.
  • أن ال Router مربوط بنقطة الدخول websecure وأنك تطلب HTTPS.
  • أن ال Container في حالة healthy إذا كان له فحص صحة، فTraefik لا يوجه الطلبات إلى Container حالته starting أو unhealthy، وتظهر الحالة في docker ps.
  • أن ال Middleware المذكور في الوسوم معرف فعلاً، فإذا أشرت إلى Middleware غير موجود ظهر ال Router في اللوحة بحالة خطأ ولم يعمل.

لا تصدر الشهادة، أو تظهر TRAEFIK DEFAULT CERT

ابحث في السجل عن سبب الرفض:

docker compose logs traefik | grep -i -A3 "acme"

غالباً سوف تجد رسالة Unable to obtain ACME certificate for domains ومعها السبب الذي أرسلته Let's Encrypt، فإذا كان السبب Timeout during connect أو Connection refused فالمنفذ 80 لا يصل إلى Traefik من الإنترنت، وعليك مراجعة جدار الحماية لدى المزود وعلى السيرفر، وإذا كان السبب NXDOMAIN أو عنواناً غير عنوان سيرفرك فسجل DNS لم ينتشر بعد أو يشير إلى مكان خاطئ، وتحقق منه بالأمر dig +short app.example.com.

وتفرض Let's Encrypt حدوداً على عدد الشهادات وعلى المحاولات الفاشلة لكل نطاق، لذلك لا تقم بإعادة تشغيل Traefik مرة بعد مرة على أمل أن ينجح الطلب، والسبب أن كل محاولة فاشلة تقربك من الحد، وإنما أصلح السبب أولاً وجرب على بيئة Staging كما وصفنا في قسم التثبيت، وإذا ظهرت رسالة too many certificates أو too many failed authorizations فلا حل إلا أن تنتظر انتهاء المدة المذكورة في الرسالة.

504 Gateway Timeout أو 502 Bad Gateway

الطلب هنا وصل إلى Traefik وطابق ال Router، ولكن Traefik لم يصل إلى ال Container، وأشيع الأسباب أن ال Container ليس على الشبكة proxy، فيحاول Traefik الوصول إليه عبر شبكة أخرى لا يراها، وينتظر 30 ثانية ثم يعيد 504، وسوف تجد في السجل تحذيراً مثل:

Could not find network named "proxy" for container "/whoami". Maybe you're missing the project's prefix in the label?

والحل أن تضيف الشبكة proxy إلى الخدمة في ملف Compose مع external: true كما في مثال whoami، وإذا كانت الخدمة على عدة شبكات فأضف إليها الوسم traefik.docker.network=proxy. أما الرمز 502 فمعناه غالباً أن المنفذ في loadbalancer.server.port خاطئ، أو أن التطبيق يستمع على 127.0.0.1 داخل ال Container وليس على 0.0.0.0.

لا يبدأ Traefik لأن المنفذ مستخدم

إذا ظهرت عند docker compose up -d رسالة مثل Bind for 0.0.0.0:80 failed: port is already allocated فهناك Container آخر ينشر المنفذ 80، وغالباً هو Nginx Proxy Manager أو Container قديم ل Traefik، أما الرسالة address already in use فمعناها أن برنامجاً على السيرفر نفسه يستمع على المنفذ مثل Nginx أو Apache، واعرف صاحب المنفذ بالأمرين التاليين:

sudo ss -ltnp | grep -E ':(80|443) '
docker ps --filter publish=80 --filter publish=443

والحل أن تختار Reverse Proxy واحداً للسيرفر، فإذا اخترت Traefik فانقل نطاقات Nginx Proxy Manager إلى وسوم في ملفات Compose، ثم أوقف NPM، ثم شغل Traefik، وإذا كان البرنامج خدمة نظام لا تحتاج إليها فأوقفها وعطلها، مثلاً sudo systemctl disable --now nginx.

يتجاهل Traefik ملف acme.json

إذا ظهرت في السجل رسالة permissions 644 for /letsencrypt/acme.json are too open, please use 600 فلن يحفظ Traefik أي شهادة، وسوف يطلبها من جديد مع كل تشغيل حتى تصطدم بالحدود، والحل أن تنفذ chmod 600 /opt/traefik/letsencrypt/acme.json ثم docker compose restart traefik، وتتكرر المشكلة عادة بعد نسخ الملف بأداة لا تحفظ الصلاحيات.

تطلب اللوحة كلمة المرور ثم ترفضها

السبب في الغالب أن علامات $ في الهاش لم تصل كما هي، فتحقق من القيمة التي وصلت إلى الوسم بالأمر docker inspect traefik --format '{{ index .Config.Labels "traefik.http.middlewares.dashboard-auth.basicauth.users" }}'، وإذا ظهر الهاش ناقصاً فضع القيمة في .env بين علامتي تنصيص مفردتين، أو ضاعف كل $ إذا كتبتها في compose.yaml، ثم نفذ docker compose up -d.

الخلاصة

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

  • يقرأ Traefik التوجيه من وسوم كل Container، فيبقى إعداد التطبيق ونطاقه وشهادته في ملف Compose واحد، ويختفي التوجيه مع التطبيق عندما تحذفه.
  • اجعل exposedByDefault: false حتى لا ينشر Traefik إلا ما تختاره، وفعل aliasHeadersStrategy: delete على نقاط الدخول المعرضة للإنترنت.
  • لا تستخدم api.insecure، وضع اللوحة خلف نطاق خاص وكلمة مرور، أو أوقفها إذا لم تحتج إليها.
  • ال Docker socket يعادل صلاحية root على السيرفر، فضع بينه وبين Traefik وسيط Socket Proxy لا يسمح إلا بالقراءة.
  • اترك تجديد الشهادات ل Traefik، وجرب على بيئة Staging أولاً، وانسخ acme.json مشفراً خارج السيرفر.

سجل التحديثات (Changelog)

  • أكتوبر 2026: كتابة الدليل.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.