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

لوحة Homepage: كل خدماتك في صفحة واحدة

صفحة واحدة تجمع روابط كل خدماتك وتعرض حالتها، بدلاً من البحث عن الروابط كل مرة. نثبت لوحة Homepage وننظمها بملفات بسيطة، ونربطها بـ Docker لعرض حالة الـ Containers بأمان.

لوحة Homepage: كل خدماتك في صفحة واحدة

لنفرض أن لديك سيرفراً Server تعمل عليه خمس خدمات Services أو ست، فسوف تجد نفسك بعد فترة قصيرة تسأل أسئلة صغيرة ولكنها مزعجة: أين رابط لوحة المراقبة Monitoring؟ وما هو النطاق الفرعي Subdomain الخاص بقاعدة المعرفة Knowledge Base؟ وهل ال Container الخاص بـ Gitea يعمل الآن أصلاً؟ والحل الأول الذي يخطر على البال هو أن تحفظ هذه الروابط في المفضلة Bookmarks داخل المتصفح Browser، وهذا الحل يعمل في اليوم الأول، ولكنه يفشل بسرعة لأن المفضلة تبقى على جهازك وحدك فلا يراها زميلك، ولا تخبرك هل الخدمة تعمل أم متوقفة، فتضغط على الرابط ثم تكتشف أن ال Container متوقف منذ يومين.

لذلك فالحل الأنسب هو لوحة بداية Dashboard تجمع روابط خدماتك في صفحة واحدة يفتحها كل الفريق، وتعرض بجانب كل رابط حالته. ومن أشهر اللوحات مفتوحة المصدر Open Source Homarr وDashy وHomepage، وسوف نشرح في هذا الدليل Homepage، وهي صفحة سريعة تعرض بجانب كل خدمة حالتها Status واستهلاكها للموارد Resources، وتضيف في أعلاها أدوات صغيرة Widgets لموارد السيرفر والبحث والوقت، وتتكامل مع أكثر من مئة تطبيق عبر واجهاتها البرمجية APIs.

وقد يتساءل البعض: لماذا Homepage وليس Homarr أو Dashy؟ والإجابة أن إعداد Homepage كله ملفات YAML، ولا توجد فيها واجهة لتحرير اللوحة UI Editor، وهذا يعني أنك تحفظ هذه الملفات في Git وتراجعها وتنسخها نسخاً احتياطياً Backup كأي نص، وهو ما يناسب من يدير سيرفره بنفسه ويفضل أن يكون الإعداد كوداً Configuration as Code. أما إذا أردت لوحة يعدلها زملاء غير تقنيين بالسحب والإفلات Drag and Drop، فإن Homarr أقرب إلى حاجتك.

وتذكر أن Homepage تعمل دون تسجيل دخول Authentication في إعدادها الافتراضي، فكل من يصل إلى رابطها يرى خدماتك، لذلك فحمايتها مسؤوليتك كما سوف نشرح لاحقاً.

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

  • تثبيت Homepage بملف Compose مع Socket Proxy للقراءة فقط، ولماذا لا نركب /var/run/docker.sock داخلها مباشرة.
  • ملفات الإعداد الخمسة، ثم الاكتشاف التلقائي للخدمات عبر وسوم Docker، وأدوات الخدمات مع حماية مفاتيحها.
  • الوصول عبر نطاق وحماية اللوحة، لأنها لا تطلب تسجيل دخول في إعدادها الافتراضي.
  • التحقق من النجاح والنسخ الاحتياطي والتحديث وأشهر المشكلات وحلولها.

ما تحتاجه قبل أن تبدأ Requirements

  • سيرفر عليه Docker وCompose، وإذا لم يكن مثبتاً فابدأ بدليل تثبيت Docker على Ubuntu.
  • الموارد: تستهلك Homepage قرابة 120 ميجابايت من الذاكرة RAM، والـ Socket Proxy قرابة 25 ميجابايت، وهذا يعني أنها تعمل بجانب خدماتك دون أن تشعر بها.
  • نطاق فرعي مثل home.example.com، وReverse Proxy مثل Nginx Proxy Manager.
  • لا تحتاج إلى فتح أي منفذ Port على الإنترنت، والسبب أن الوصول كله يمر عبر ال Reverse Proxy.

تثبيت Homepage Installation

لماذا نضع Socket Proxy بدلاً من تركيب ال socket مباشرة؟

لكي تعرض Homepage حالة ال Containers فهي تحتاج إلى أن تسأل Docker عنها Query، والطريقة الأبسط التي تجدها في أغلب الأمثلة هي أن تركب Mount الملف /var/run/docker.sock داخلها، والمشكلة في هذه الطريقة أن من يصل إلى هذا الملف يستطيع أن يطلب من Docker إنشاء Container بصلاحيات Permissions حساب root على السيرفر، كما يشرح توثيق أمان Docker Security، وبالتالي تصبح أي ثغرة في اللوحة باباً إلى السيرفر كله. وقد تظن أن العلامة :ro تحل المشكلة، ولكنها لا تغير شيئاً هنا، والسبب أنها تمنع تعديل الملف نفسه ولا تمنع إرسال الأوامر عبره، بالإضافة إلى أن Homepage تحتاج عندها إلى العمل بـ root حتى تقرأ ال socket.

لذلك فالحل الأنسب أن نضع بينهما docker-socket-proxy، وهو Container صغير يركب ال socket ويقدم للشبكة الداخلية Internal Network واجهة Docker محدودة الصلاحيات، حيث نسمح فيه بقراءة ال Containers فقط (CONTAINERS=1) ونمنع كل طلبات الكتابة Write Requests (POST=0)، وهما الإعدادان اللذان يكفيان Homepage، أما SERVICES وTASKS التي تراها في أمثلة التوثيق فلا تحتاج إليها إلا في Docker Swarm. وبما أن Homepage لا تتصل بال socket مباشرة، فنستطيع تشغيلها بمستخدم عادي Non-root User وليس بـ root.

ملف Compose

الآن سوف نقوم بإنشاء مجلد الإعداد ونجعله ملكاً للمستخدم 1000:

sudo mkdir -p /opt/homepage/config
sudo chown -R 1000:1000 /opt/homepage/config
cd /opt/homepage

ثم أنشئ الملف /opt/homepage/compose.yaml كما يلي، حيث إن الشبكة proxy هي الشبكة الخارجية External التي يعمل عليها ال Reverse Proxy، وإذا لم تكن موجودة فأنشئها بالأمر docker network create proxy، أما الشبكة internal فهي شبكة داخلية بين Homepage وال Socket Proxy فقط ولا تتصل بالإنترنت:

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

  homepage:
    image: ghcr.io/gethomepage/homepage:v2.4.0
    container_name: homepage
    restart: unless-stopped
    environment:
      HOMEPAGE_ALLOWED_HOSTS: home.example.com
      PUID: 1000
      PGID: 1000
    volumes:
      - ./config:/app/config
    networks:
      - internal
      - proxy
    depends_on:
      - dockerproxy

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

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

  • المتغير HOMEPAGE_ALLOWED_HOSTS إلزامي منذ الإصدار 1.0 حتى تفتح اللوحة باسم غير localhost، وتكتب فيه أسماء المضيف Hostnames التي تفتح بها اللوحة مفصولة بفواصل دون مسافات، مع المنفذ إن وجد، وأي اسم آخر ترفضه Homepage بالرمز 400، والسبب أن هذا الفحص يحمي واجهتها الداخلية من هجمات DNS rebinding. ولاحظ أن localhost:3000 و127.0.0.1:3000 مسموحان دائماً، لذلك لا تحتاج إلى إضافتهما للاختبار المحلي Local Testing.
  • المتغيران PUID وPGID يشغلان العملية Process بالمستخدم 1000 وليس بـ root، ولهذا جعلنا مجلد الإعداد ملكاً لهذا المستخدم، ولاحظ أن Homepage تصحح ملكية المجلد بنفسها عند التشغيل وتكتب ذلك في السجل بالعبارة Fixing ownership of /app/config.
  • ثبتنا الوسم Tag بالرقم الكامل بدلاً من latest الذي تستخدمه أمثلة التوثيق، والسبب أن الإصدار لن يتغير دون علمك عند أول docker compose pull.
  • لم ننشر أي منفذ لا لـ Homepage ولا لل Socket Proxy، فالوصول إلى اللوحة عبر ال Reverse Proxy فقط، وال Socket Proxy لا يراه أحد غير Homepage على الشبكة internal. وفي ال Image فحص صحة Healthcheck جاهز على المسار /api/healthcheck.

بعد ذلك شغل الخدمة وراجع السجل Log:

docker compose up -d
docker compose ps
docker compose logs --tail 20 homepage

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

NAME                   STATUS
homepage               Up 30 seconds (healthy)
homepage-dockerproxy   Up 31 seconds
...
✓ Ready in 0ms
[info]: settings.yaml was copied to the config folder

وفي التشغيل الأول تنسخ Homepage ملفات إعداد نموذجية إلى config/، وسوف نضع ملفاتنا مكانها في القسم التالي.

ملفات الإعداد Configuration Files

كل ملف مسؤول عن جزء من الصفحة، وتقرأ Homepage الملفات من جديد كلما حدثت الصفحة، وبالتالي لا تحتاج إلى إعادة تشغيل Restart ال Container بعد كل تعديل. والشكل التالي يبين النتيجة التي سوف نصل إليها:

لوحة Homepage بسمة داكنة تعرض موارد الخادم والبحث والتاريخ ومجموعتي خدمات وإشارات مرجعية
اللوحة بعد الإعداد: أدوات المعلومات Info Widgets في الأعلى، ثم مجموعات الخدمات Groups، ثم الإشارات المرجعية Bookmarks

settings.yaml: العنوان والسمة وترتيب المجموعات

نبدأ بالملف الذي يحدد عنوان الصفحة Title وسمتها Theme وتخطيطها Layout:

title: بوابة الفريق
theme: dark
color: slate
headerStyle: boxed
statusStyle: dot
layout:
  Applications:
    style: row
    columns: 3
  Infrastructure:
    style: row
    columns: 3

في الإعداد أعلاه لاحظ أن القسم layout يحدد ترتيب المجموعات وشكلها، والأسماء فيه يجب أن تطابق أسماء المجموعات في services.yaml حرفاً بحرف.

docker.yaml: الاتصال بـ Docker

في هذا الملف نعرف خادم Docker باسم نستخدمه في بقية الملفات، ونوجهه إلى ال Socket Proxy باسم ال Container الخاص به على الشبكة الداخلية، كما في توثيق إعداد Docker:

my-docker:
  host: dockerproxy
  port: 2375

وإذا فضلت تركيب ال socket مباشرة في ال Container الخاص بـ Homepage، وقبلت المخاطرة التي شرحناها وتشغيلها بـ root، فاكتب socket: /var/run/docker.sock مكان host وport، ولكننا لا ننصح بذلك. ولإضافة سيرفر ثان، أضف تعريفاً آخر يشير إلى Socket Proxy على ذلك السيرفر عبر شبكة خاصة Private Network، ولا تنشر المنفذ 2375 على عنوان عام، والسبب أن أي منفذ يجيب عن Docker API هو نافذة على كل ما يعمل في السيرفر.

services.yaml: الخدمات

هذا الملف مقسم إلى مجموعات، وفي كل مجموعة عدد من الخدمات، ولكل خدمة اسم ورابط ووصف Description وأيقونة Icon، وتستطيع ربطها بـ Container حتى تعرض حالته:

- Applications:
    - Git:
        href: https://git.example.com
        description: مستودعات الشيفرة
        icon: gitea.svg
    - Wiki:
        href: https://wiki.example.com
        description: قاعدة معرفة الفريق
        icon: outline.svg

- Infrastructure:
    - Portainer:
        href: https://portainer.example.com
        description: إدارة الحاويات
        icon: portainer.svg
    - Nginx Proxy Manager:
        href: https://npm.example.com
        description: النطاقات والشهادات
        icon: nginx-proxy-manager.svg
    - Homepage:
        href: https://home.example.com
        description: هذه اللوحة
        icon: homepage.png
        server: my-docker
        container: homepage

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

  • الأيقونات: إذا كتبت اسم ملف مثل gitea.svg فسوف تجلبه Homepage من مجموعة dashboard-icons، وهي تقبل أيضاً البادئة Prefix mdi- لأيقونات Material Design، والبادئة si- لأيقونات Simple Icons (مثل si-github)، أو رابطاً كاملاً لملف أيقونة. وانتبه إلى الامتداد Extension، والسبب أن بعض الأيقونات متوفر بصيغة png فقط، ومنها أيقونة Homepage نفسها.
  • server وcontainer: هذان الحقلان يربطان البطاقة Card بـ Container، فتظهر شارة الحالة Badge (RUNNING، أو HEALTHY إذا كان لل Container فحص صحة)، ومعها زر يعرض استهلاك المعالج CPU والذاكرة والشبكة.
بطاقة خدمة في Homepage مع شارة HEALTHY وإحصاءات CPU وMEM وRX وTX
إحصاءات ال Container المرتبط بالبطاقة، ومصدرها Docker عبر ال Socket Proxy

الاكتشاف التلقائي للخدمات Auto Discovery بوسوم Docker

كتابة كل خدمة في services.yaml تعمل جيداً عندما تكون الخدمات قليلة، ولكنك سوف تنسى مع الوقت أن تحذف خدمة أزلتها أو تضيف خدمة جديدة، فتصبح اللوحة غير مطابقة لما يعمل فعلاً. لذلك تستطيع الخدمة أن تعلن عن نفسها بوسوم Labels في ملف Compose الخاص بها، فتظهر في اللوحة ما دامت تعمل وتختفي عندما تحذفها. لنأخذ مثالاً بالتطبيق whoami، حيث أضفنا إليه الخيار siteMonitor الذي يطلب الرابط بشكل دوري ويعرض زمن الاستجابة Latency:

services:
  whoami:
    image: traefik/whoami:v1.11.0
    container_name: whoami
    restart: unless-stopped
    labels:
      homepage.group: Applications
      homepage.name: Whoami
      homepage.icon: traefik.svg
      homepage.href: https://whoami.example.com
      homepage.description: تطبيق اختبار الوكيل
      homepage.server: my-docker
      homepage.container: whoami
      homepage.siteMonitor: http://whoami:80
    networks:
      - homepage_internal

networks:
  homepage_internal:
    external: true

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

  • الوسمان homepage.server وhomepage.container اختياريان هنا، والسبب أن Homepage تستنتجهما تلقائياً من ال Container الذي يحمل الوسوم، ولكننا كتبناهما للوضوح.
  • لكي يصل siteMonitor إلى ال Container باسمه، يجب أن يكون ال Container على شبكة تصل إليها Homepage، واسم الشبكة الداخلية من ملفنا يصبح homepage_internal، لأن Compose يضيف اسم المشروع Project Name في أوله. وإذا كان الرابط العام يعمل، فيكفيك siteMonitor: https://whoami.example.com دون مشاركة الشبكة.
  • في لقطة الشاشة Screenshot الأولى سوف ترى بطاقة Whoami وعليها 3 MS وRUNNING.

widgets.yaml: أدوات المعلومات

هذا الملف يحدد الشريط العلوي في الصفحة، وفيه موارد السيرفر وصندوق البحث Search Box والتاريخ والوقت:

- resources:
    cpu: true
    memory: true
    disk: /
- search:
    provider: duckduckgo
    target: _blank
- datetime:
    text_size: xl
    format:
      timeStyle: short
      dateStyle: long

ولاحظ أن أداة resources تقيس الموارد كما يراها ال Container، فالقرص / هنا هو نظام الملفات Filesystem الخاص بال Container، وغالباً هو قرص Docker على السيرفر. ولكي تقيس قرص بيانات آخر، قم بتركيبه في ال Container للقراءة فقط Read-only (مثل /srv:/srv:ro)، ثم اكتب disk: /srv.

bookmarks.yaml: الإشارات المرجعية

وهنا تضع الروابط التي كنت تحفظها في مفضلة المتصفح، ولكنها الآن في مكان يراه كل الفريق:

- Docs:
    - Docker:
        - abbr: DK
          href: https://docs.docker.com/
    - Ubuntu Server:
        - abbr: UB
          href: https://ubuntu.com/server/docs
- Community:
    - ArabRoot:
        - abbr: AR
          href: https://example.com/
    - GitHub:
        - icon: github.svg
          href: https://github.com/

وكل إشارة تحمل اختصاراً Abbreviation من حرفين (abbr) أو أيقونة.

أدوات الخدمات Service Widgets والأسرار Secrets

لكل تطبيق مدعوم أداة تعرض أرقامه داخل البطاقة، مثل عدد ال Containers في Portainer أو عدد المضيفين في Nginx Proxy Manager، وتحتاج الأداة عادة إلى رابط داخلي ومفتاح API. ولا تكتب المفاتيح في YAML مباشرة، والسبب أن ملفات YAML تذهب إلى Git وإلى النسخ الاحتياطية، وإنما مررها كمتغيرات بيئة Environment Variables تبدأ بـ HOMEPAGE_VAR_، ثم أشر إليها بين قوسين مزدوجين. والمثال التالي من التوثيق الرسمي لأداة Portainer:

    - Portainer:
        href: https://portainer.example.com
        icon: portainer.svg
        widget:
          type: portainer
          url: https://portainer:9443
          env: 1
          key: "{{HOMEPAGE_VAR_PORTAINER_KEY}}"

بعد ذلك أضف في compose.yaml السطر HOMEPAGE_VAR_PORTAINER_KEY: ${PORTAINER_KEY} تحت environment، وضع القيمة في ملف .env بالصلاحية 600، وأنشئ للأداة مستخدماً أو مفتاحاً بصلاحية قراءة فقط، والسبب أن هذا المفتاح سوف يكون محفوظاً في اللوحة، فإذا تسرب فلن يستطيع من يملكه أن يغير شيئاً.

وتتكيف اللوحة مع شاشة الهاتف Mobile تلقائياً، فتصطف المجموعات عمودياً كما في الشكل التالي:

لوحة Homepage على شاشة بعرض هاتف
العرض على الهاتف

الوصول عبر نطاق Domain وحماية اللوحة

أنشئ في Nginx Proxy Manager مضيفاً Proxy Host للنطاق home.example.com يوجه الطلبات Requests إلى http://homepage:3000، والسبب أن ال Containers الاثنين على الشبكة proxy، ثم اطلب شهادة Certificate من Let's Encrypt وفعل Force SSL. بعد ذلك تأكد أن الاسم نفسه موجود في HOMEPAGE_ALLOWED_HOSTS، وإلا ظهرت لك صفحة فارغة أو الرمز 400.

🛑
لا تطلب Homepage تسجيل دخول في إعدادها الافتراضي، لذلك يرى كل من يفتح الرابط أسماء خدماتك وروابطها الداخلية وحالة ال Containers لديك وأي أرقام تعرضها أدوات الخدمات، فلا تتحها للعامة دون حماية، وإنما قيدها بقائمة وصول Access List في ال Reverse Proxy (عناوين المكتب أو كلمة مرور Password)، أو اجعل الوصول إليها عبر شبكة خاصة افتراضية VPN كما في دليل شبكة خاصة مع WireGuard وwg-easy، أو ضعها خلف دخول موحد SSO كما في دليل حماية أي تطبيق باستخدام Authentik Forward Auth.

وقد تسأل: ألا توجد في Homepage حماية خاصة بها؟ والإجابة أن الإصدار 2.0 أضاف بوابة دخول بسيطة بكلمة مرور أو عبر OIDC، ولكنها معطلة حتى تضيف المتغير HOMEPAGE_AUTH_ENABLED=true ومعه HOMEPAGE_AUTH_SECRET وHOMEPAGE_EXTERNAL_URL وكلمة المرور في HOMEPAGE_AUTH_PASSWORD. ويجدر الإشارة هنا أن التوثيق نفسه ما زال يوصي بوضع اللوحة خلف Reverse Proxy يفرض الدخول أو خلف VPN، والسبب أن هذه البوابة لا تحد من عدد محاولات كلمة المرور Rate Limiting، لذلك اعتبرها طبقة إضافية وليست بديلاً عن Forward Auth أو VPN.

📌
في يونيو 2024 نشر فريق Homepage التحذير الأمني GHSA-24m5-7vjx-9x37 بدرجة خطورة حرجة Critical، حيث تبين أن عدة تكاملات Integrations في الإصدارات قبل 0.9.1 تسمح بأن تطلب من الخدمات المرتبطة باللوحة واجهات API غير التي صممت لها الأداة وتقرأ ردودها، وهذا قد يكشف مفاتيح API وكلمات المرور والإعدادات الداخلية، بل قد يصل إلى تنفيذ الأوامر عن بعد Remote Code Execution. والدرس هنا أن اللوحة التي تحمل مفاتيح خدماتك هي نفسها هدف للمخترق، فلا تتركها مكشوفة للعامة، وقم بتحديثها عند صدور أي تحذير أمني.

كيف تتأكد أن كل شيء يعمل؟ Verification

  • الأمر docker compose ps يعرض ال Container homepage بالحالة healthy.
  • اللوحة تفتح على https://home.example.com، والطلب باسم غير مسموح يعود بالرمز 400، ويمكنك اختبار ذلك من Container على الشبكة نفسها بالأمر curl -s -o /dev/null -w '%{http_code}' -H 'Host: other.example.net' http://homepage:3000/.
  • البطاقات المرتبطة بـ Containers تظهر بالحالة RUNNING أو HEALTHY، وإذا أوقفت Container للتجربة فسوف ترى الشارة تتغير.
  • العملية تعمل بمستخدم غير root، حيث يعرض الأمر docker compose exec homepage ps -o user,comm المستخدم node أمام next-server.
  • ال Socket Proxy يرفض طلبات الكتابة، ويمكنك أن تتأكد من ذلك من داخل ال Container الخاص بـ Homepage بطلب إيقاف Container:
docker compose exec homepage wget -q -O- --post-data="" http://dockerproxy:2375/containers/whoami/stop

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

wget: server returned error: HTTP/1.0 403 Forbidden

بل حتى طلب قائمة ال Images (/images/json) يعود بالرمز 403، والسبب أننا سمحنا بال Containers وحدها، وبهذا الشكل نكون قد تأكدنا أن ثغرة في اللوحة لن تمنح المخترق تحكماً في Docker.

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

كل ما تحتاجه Homepage موجود في المجلد config/ والملف compose.yaml، والملف .env إن وجد، ولا توجد فيها قاعدة بيانات Database، لذلك لا تحتاج إلى إيقاف الخدمة قبل النسخ:

cd /opt
sudo tar czf homepage-$(date +%F).tar.gz --exclude=homepage/config/logs homepage

والأفضل أن تجعل /opt/homepage مستودع Git خاصاً Repository، وتستثني .env وconfig/logs في .gitignore، فيبقى لديك سجل بكل تعديل ومن قام به. ثم انقل الأرشيف Archive إلى خارج السيرفر، والسبب أن النسخة المحفوظة على نفس القرص لن تنفعك إذا فقدت القرص نفسه. وللاستعادة فك الأرشيف في /opt، وتأكد أن ملكية Ownership المجلد config/ هي 1000:1000، ثم نفذ docker compose up -d.

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

  1. راجع صفحة الإصدارات Releases، خاصة عندما يتغير رقم الإصدار الرئيسي Major Version، والسبب أن بعض الأدوات تغير أسماء حقولها بين الإصدارات.
  2. خذ نسخة من config/ كما في القسم السابق.
  3. غير الوسم في compose.yaml، وغير وسم ال Socket Proxy إذا صدر له إصدار أحدث، ثم نفذ:
cd /opt/homepage
docker compose pull
docker compose up -d
docker compose logs --tail 30 homepage

بعد ذلك ابحث في السجل عن أخطاء في قراءة YAML أو تحذيرات عن حقول لم تعد مدعومة، وإذا ظهرت مشكلة فالرجوع إلى الإصدار السابق Rollback سهل، حيث تعيد الوسم القديم وتنفذ docker compose up -d من جديد.

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

صفحة فارغة أو الرسالة Host validation failed

السبب أن اسم المضيف الذي فتحت به اللوحة، ومعه المنفذ إن كتبته، غير موجود في HOMEPAGE_ALLOWED_HOSTS، وسوف تجد الاسم المرفوض في السجل بعد العبارة Host validation failed for:، فأضفه كما هو وأعد إنشاء ال Container بالأمر docker compose up -d. وتجد حالات أخرى في صفحة استكشاف الأخطاء.

لماذا تعرض بطاقة ال Container خطأ أو لا تظهر حالتها؟

  • قيمة container يجب أن تطابق اسم ال Container كما يظهر في docker ps، وليس اسم الخدمة في Compose، ولهذا نستخدم container_name.
  • قيمة server يجب أن تطابق اسماً معرفاً في docker.yaml.
  • تأكد أن Homepage وال Socket Proxy على الشبكة نفسها، حيث يجب أن يعيد الأمر docker compose exec homepage wget -q -O- http://dockerproxy:2375/version بيانات JSON.
  • الرمز 403 في السجل يعني أن ال Socket Proxy يمنع ما تطلبه Homepage، ولاحظ أن الإعداد CONTAINERS=1 يكفي لعرض الحالة والإحصاءات، فلا تفتح صلاحيات أخرى لحل هذه المشكلة.

لماذا لا تظهر الخدمات المعرفة بالوسوم؟

الاكتشاف يحتاج إلى ملف docker.yaml معرف، وإلى Container يحمل على الأقل الوسمين homepage.group وhomepage.name، ويمكنك أن تتحقق من الوسوم بالأمر docker inspect whoami --format '{{json .Config.Labels}}'. وتذكر أن Docker يقرأ الوسوم عند إنشاء ال Container فقط، لذلك فأي تعديل عليها يحتاج إلى docker compose up -d لل Container المعني.

لا تظهر الأيقونات

السبب إما أن الاسم أو الامتداد غير موجود في مجموعة الأيقونات (مثل homepage.svg مكان homepage.png)، وإما أن متصفح المستخدم لا يصل إلى شبكة CDN التي تأتي منها الأيقونات. وفي البيئات المعزولة Air-gapped ضع الأيقونات في مجلد تركبه على /app/public/icons (مثل ./icons:/app/public/icons:ro)، ثم أشر إليها بـ /icons/اسم-الملف.svg.

خطأ في YAML يعطل الصفحة

مسافة زائدة واحدة أو علامة تبويب Tab واحدة تكفي لتعطيل الصفحة، لذلك افحص الملف قبل الحفظ بالأمر docker run --rm -v "$PWD/config":/c:ro mikefarah/yq:4.53.6 '.' /c/services.yaml (أداة yq)، أو بأي أداة فحص Linter لملفات YAML في المحرر Editor الذي تستخدمه، ثم راجع docker compose logs homepage.

الخلاصة

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

  • المفضلة في المتصفح لا يراها الفريق ولا تخبرك بحالة الخدمات، أما Homepage فتجمع الروابط وحالة ال Containers في صفحة واحدة، وكل إعدادها ملفات YAML تحفظها في Git.
  • لا تركب /var/run/docker.sock داخل اللوحة، وإنما ضع بينهما Socket Proxy بالإعداد CONTAINERS=1 وPOST=0 على شبكة داخلية، وشغل Homepage بمستخدم غير root.
  • المتغير HOMEPAGE_ALLOWED_HOSTS إلزامي، وأي اسم غير موجود فيه يعود بالرمز 400.
  • لا تطلب Homepage تسجيل دخول في إعدادها الافتراضي، لذلك ضعها خلف Forward Auth أو VPN أو قائمة وصول، ومرر مفاتيح الأدوات عبر متغيرات HOMEPAGE_VAR_ بصلاحية قراءة فقط.
  • انسخ config/ وcompose.yaml و.env إلى خارج السيرفر، وثبت الوسم بالرقم الكامل وراجع صفحة الإصدارات قبل كل تحديث.

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

  • سبتمبر 2026: كتابة الدليل واختباره على Homepage v2.4.0.
  • أكتوبر 2026: مراجعة الدليل واختباره من جديد على الإصدار نفسه، وتصحيح ما يخص تسجيل الدخول (بوابة الدخول التي أضافها الإصدار 2.0 ومعطلة افتراضياً)، وتوضيح أن localhost:3000 مسموح دائماً، وأن وسمي server وcontainer اختياريان في الاكتشاف التلقائي، وإضافة رابط دليل WireGuard.
نشرة عرب رووت | ArabRoot

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

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

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

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