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

# هندسة المنصات Platform Engineering والمشكلة التي تحلها
- URL: https://arabroot.io/articles/هندسة-المنصات-platform-engineering/
- Published: 2026-10-10T12:30:00.000Z
- Updated: 2026-10-09T14:19:44.000Z
- Description: لماذا تبني الشركات منصة داخلية لمطوريها ومتى لا تحتاجها، مع قالب في Backstage ينشئ خدمة جاهزة بخط CI.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية

لنفرض أن مطوراً في فريقك يريد أن يبدأ خدمة جديدة صغيرة، فيحتاج إلى مستودع Git، وخط CI يختبر الكود، وبيئة اختبار Staging، وقاعدة بيانات، ومراقبة وتنبيه، وفي أغلب الشركات يفتح لكل واحد من هذه الأشياء تذكرة Ticket عند فريق التشغيل، ثم ينتظر، وعندما تصل الخدمة أخيراً إلى الإنتاج تجد أنها مبنية بطريقة تختلف عن الخدمة التي بجانبها لأن من أعدها شخص آخر في شهر آخر. وهذه المشكلة لا تظهر في شركة من خمسة مطورين، ولكنها تظهر بوضوح عندما يصبح عدد الفرق خمسة أو عشرة، وهنا يأتي مصطلح هندسة المنصات Platform Engineering الذي انتشر في السنوات الأخيرة حتى أصبح عنواناً لوظائف وفرق ومؤتمرات.

وفي [طرق نشر التطبيقات من FTP إلى GitOps](https://arabroot.io/articles/%D8%B7%D8%B1%D9%82-%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%85%D9%86-ftp-%D8%A7%D9%84%D9%89-gitops/) رأينا كيف يتطور النشر نفسه، وفي [خارطة CTO لشركة SaaS ناشئة](https://arabroot.io/articles/%D8%AE%D8%A7%D8%B1%D8%B7%D8%A9-cto-%D9%84%D8%B4%D8%B1%D9%83%D8%A9-saas-%D9%86%D8%A7%D8%B4%D8%A6%D8%A9-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%81%D8%B1%D9%82/) ذكرنا متى توظف أول مهندس منصة، وفي هذا المقال نشرح الفكرة نفسها: ما المشكلة التي تحلها، وما الذي تبنيه الشركات تحت هذا الاسم، ومتى لا تحتاجه شركتك أصلاً. وسوف نناقش في هذا المقال ما يلي:

- المشكلة: الحمل الذهني على فرق المنتج، والتشغيل عبر التذاكر، والسيرفرات التي لا تشبه بعضها.
- ما هي هندسة المنصات، والفرق بين المنصة الداخلية للمطورين Internal Developer Platform وبوابة المطورين Internal Developer Portal.
- الطرق الممهدة Golden Paths، والمنصة كمنتج Platform as a Product.
- فريق المنصة في كتاب Team Topologies، وما تقوله ورقة المنصات ونموذج النضج من CNCF.
- الأدوات: Backstage مفتوح المصدر، وPort وHumanitec كخيارات تجارية.
- تجربة عملية: قالب في Backstage ينشئ مستودعاً في Gitea مع خط CI يعمل على Gitea Actions، مع حدود هذا الربط كما ظهرت لنا.
- متى لا تحتاج شركتك فريق منصة.

## المشكلة: حمل ذهني وتذاكر وسيرفرات لا تشبه بعضها

المشكلة الأولى هي الحمل الذهني Cognitive Load، فالمطور الذي يكتب منطق الطلبات أو الدفع في شركتك يطلب منه اليوم أن يعرف أيضاً Docker وKubernetes وشهادات TLS وإعداد خط CI والمراقبة وفحوصات الأمن، وكتاب Team Topologies يبني فكرته كلها على أن قدرة الفريق على حمل التعقيد محدودة، وأن [الفريق ينهار إذا أضفت إلى ما يحمله دون أن ترفع عنه شيئاً](https://teamtopologies.com/key-concepts?ref=arabroot.io). والنتيجة المعروفة أن الفريق يقضي جزءاً كبيراً من أسبوعه على أشياء لا علاقة لها بالمنتج، وكل فريق يحل المشكلة نفسها بطريقته.

والمشكلة الثانية هي التشغيل عبر التذاكر، وهو الحل البسيط الذي تصل إليه أغلب الشركات أولاً: فريق تشغيل مركزي Ops يستقبل الطلبات وينفذها بيده، وهذا يعمل في البداية ثم يتحول الفريق إلى طابور انتظار، والسبب أن عدد الطلبات يكبر مع عدد الفرق بينما فريق التشغيل لا يكبر بالسرعة نفسها. وتسمي [صفحة DORA عن هندسة المنصات](https://dora.dev/capabilities/platform-engineering/?ref=arabroot.io) هذا الخطأ «فخ التشغيل بالتذاكر» Ticket-ops Trap، وتصف الفريق فيه بأنه آلة بيع للبنية التحتية تبقى عالقة في العمل اليدوي المتكرر بدلاً من أن تمكن الفرق من خدمة نفسها.

والمشكلة الثالثة هي السيرفرات التي لا تشبه بعضها Snowflake Servers، فكل سيرفر أعده شخص مختلف بطريقة مختلفة، وهذا بناه بيده، وذاك بسكربت قديم، والثالث نسخ إعداد سيرفر آخر وعدل عليه، وعندما يتعطل أحدها لا يعرف أحد كيف يعاد بناؤه، وقد شرحنا علاج هذه المشكلة على مستوى البنية في [البنية كشيفرة IaC للمبتدئين](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-%D9%83%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-iac-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).

والصورة التالية تبين الوضعين: على اليمين كل فريق يرسل تذاكر إلى فريق تشغيل واحد، وعلى اليسار الفرق نفسها تخدم نفسها من منصة يبنيها فريق متخصص:

![مخطط من جزأين. على اليمين بعنوان OPS BY TICKET ثلاثة فرق Team A وTeam B وTeam C ترسل أسهماً حمراء إلى صندوق ticket queue فيه تذاكر، ومنه إلى Ops team مكتوب تحته bottleneck، ثم ثلاثة سيرفرات تحتها built by hand وold script وcopied config، وفي الأسفل waiting on tickets وevery server built differently. وعلى اليسار بعنوان SELF-SERVICE الفرق الثلاثة ترسل أسهماً خضراء إلى صندوق Platform فيه Templates وCI/CD pipelines وEnvironments وObservability وSecrets databases، وبجانبه Platform team مكتوب تحته builds the product، وتحت المنصة ثلاثة سيرفرات متشابهة same standard base](https://arabroot.io/content/images/2026/10/platform-engineering-01-ticket-ops.webp)

التذكرة تنقل العمل من فريق إلى آخر، أما المنصة فتلغي الحاجة إلى التذكرة نفسها

وقد يتساءل البعض: لماذا لا نترك كل فريق يدير بنيته بنفسه بالكامل، على قاعدة «من يبني الخدمة يشغلها» You Build It, You Run It؟ والإجابة أن هذه القاعدة صحيحة من ناحية المسؤولية، ولكنها إذا طبقت دون أدوات مشتركة أعادت المشكلة الأولى، فكل فريق يحمل البنية كلها وحده ويعيد بناء الشيء نفسه عشر مرات، ولذلك فالحل الأنسب ليس فريق تشغيل ينفذ الطلبات ولا فرقاً تحمل كل شيء، وإنما فريق يبني أدوات مشتركة يستخدمها الآخرون بأنفسهم، وهذا هو تعريف هندسة المنصات.

## ما هي هندسة المنصات؟

تعرف [ورقة المنصات Platforms White Paper](https://tag-app-delivery.cncf.io/whitepapers/platforms/?ref=arabroot.io) التي أصدرتها مجموعة TAG App Delivery في CNCF المنصة بأنها مجموعة متكاملة من القدرات Capabilities تعرف وتقدم بحسب احتياجات مستخدميها، أي أساس من واجهات برمجية API وأدوات وخدمات ومعرفة ودعم مرتبة كمنتج داخلي جذاب، وتعرف هندسة المنصات Platform Engineering بأنها ممارسة بناء هذه المنصات وتشغيلها. أما [DORA](https://dora.dev/capabilities/platform-engineering/?ref=arabroot.io) فتصفها بأنها تخصص اجتماعي تقني Sociotechnical يعمل في نقطة التقاء العلاقات بين الفرق مع الجوانب التقنية للأتمتة والخدمة الذاتية Self-service والتكرار المضمون.

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

ومن الأرقام التي تبين حجم انتشار الفكرة ما تذكره صفحة DORA نفسها: في سنة 2025 قالت 90% من المؤسسات في استبيانها إنها تستخدم منصة داخلية للمطورين، وقالت 76% إن لديها فريق منصة مخصصاً. ولاحظ أن هذا استبيان على المؤسسات التي شاركت في أبحاث DORA، وأغلبها شركات متوسطة وكبيرة، فلا تقرأه على أنه حال كل شركة ناشئة.

## المنصة الداخلية IDP وبوابة المطورين: ما الفرق؟

هنا يقع خلط كثير، والسبب أن الاختصار IDP يستخدم للاسمين: المنصة الداخلية للمطورين Internal Developer Platform، وبوابة المطورين الداخلية Internal Developer Portal، وهما شيئان مختلفان:

- **المنصة Internal Developer Platform:** هي القدرات نفسها: إنشاء مستودع بخط CI جاهز، والنشر إلى البيئات، وطلب قاعدة بيانات، والأسرار، والمراقبة، وفحوصات الأمن. وتقدم هذه القدرات عبر واجهات يستخدمها المطور مباشرة، مثل `git push` وسطر الأوامر CLI وال API.
- **البوابة Internal Developer Portal:** هي الواجهة الرسومية التي تجمع هذه القدرات في مكان واحد: فهرس الخدمات Software Catalog ومالك كل خدمة، والقوالب Templates، والتوثيق، والبحث. وتعرف [Backstage نفسها](https://backstage.io/docs/overview/what-is-backstage?ref=arabroot.io) بأنها إطار مفتوح المصدر لبناء بوابات المطورين Developer Portals.

والصورة التالية تبين العلاقة بين الطبقتين:

![ثلاث طبقات فوق بعضها. في الأعلى صندوق أزرق Internal Developer Portal مكتوب تحته the front door: Backstage, Port, Humanitec Portal وفيه Software Catalog وTemplates وDocs TechDocs وSearch ownership. في الوسط صندوق أخضر Internal Developer Platform مكتوب تحته capabilities offered as self-service APIs, CLI and Git وفيه Build and deploy CI/CD وEnvironments وDatabases queues وObservability وSecrets identity وSecurity checks. وفي الأسفل Infrastructure فيها سحابة وسيرفرات وKubernetes VMs network. وعلى اليمين حاسوب Developer يصل إلى البوابة بسهم via portal وإلى المنصة مباشرة بسهم or directly: git push, CLI, API](https://arabroot.io/content/images/2026/10/platform-engineering-02-platform-vs-portal.webp)

البوابة واجهة اختيارية فوق المنصة، والمطور يستطيع أن يصل إلى المنصة مباشرة بدونها

في الصورة أعلاه لاحظ التالي:

- المنصة تعمل بدون بوابة، فخط CI يعمل مع كل `git push` سواء كانت هناك واجهة رسومية أو لم تكن.
- البوابة بدون منصة تحتها مجرد فهرس روابط وتوثيق، وهذا مفيد ولكنه لا يرفع العمل عن أحد، ولذلك لا تبدأ ببناء Backstage قبل أن تعرف ما القدرات التي سوف تعرضها فيه.
- البنية التحتية Infrastructure، أي السحابة والسيرفرات وKubernetes، ليست المنصة نفسها، وإنما ما تبنى عليه المنصة.

## الطرق الممهدة Golden Paths

المصطلح الذي يتكرر مع هندسة المنصات هو الطريق الممهد Golden Path، ويسميه البعض Paved Road، وقد شرحت Spotify في [مقالها عن الطرق الممهدة](https://engineering.atspotify.com/2020/08/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem?ref=arabroot.io) أن المشكلة عندها كانت أن المهندس الجديد يتعلم كيف يبني خدمة بسؤال زملائه، وسمت ذلك «التطوير بالإشاعة» Rumour-driven Development، ثم عرفت الطريق الممهد بأنه الطريق «صاحب الرأي والمدعوم» Opinionated and Supported لبناء شيء ما، مثل خدمة Backend أو موقع أو خط بيانات.

والكلمتان في التعريف تحملان الفكرة كلها: «صاحب الرأي» يعني أن الطريق اختار لك اللغة والإطار وطريقة البناء والنشر والمراقبة، فلا تقضي أسبوعاً في الاختيار، و«المدعوم» يعني أن فريق المنصة مسؤول عن هذا الطريق، فإذا تعطل خط CI أو ظهرت ثغرة في Docker Image الأساسية فهو من يصلحها للجميع. والصورة التالية تبين الطريق من فكرة الخدمة إلى الإنتاج:

![طريق أفقي أصفر من اليمين إلى اليسار يبدأ بحاسوب new service idea وينتهي بعلامة صح in production، وعليه أربع محطات مرقمة: 1 Pick a template مع language framework owner، و2 Repo with CI ready مع tests scans Dockerfile، و3 Deploy to environments مع staging then production، و4 Monitored and in catalog مع dashboards alerts owner. ومن الطريق يخرج سهم متقطع رمادي إلى صندوق Off the path is allowed but the team owns its own support، وفي صندوق أصفر opinionated and supported (Spotify's definition of a Golden Path)](https://arabroot.io/content/images/2026/10/platform-engineering-03-golden-path.webp)

الطريق الممهد يجعل الخيار الصحيح هو الأسهل، ولا يمنع من يحتاج طريقاً آخر

ولاحظ السهم المتقطع في الصورة، فالطريق الممهد ليس إلزامياً، والورقة البيضاء من CNCF تضع بين صفات المنصة الجيدة أن تكون قدراتها «اختيارية وقابلة للتركيب» Optional and Composable، فالفريق الذي يحتاج شيئاً مختلفاً يستطيع أن يخرج عن الطريق، ولكنه يتحمل دعم ما اختاره بنفسه. وإذا جعلت الطريق إلزامياً بالأوامر فقد بنيت ما تسميه DORA «القفص الذهبي» Golden Cage، وهو أحد الأخطاء التي سوف نذكرها لاحقاً.

## المنصة كمنتج Platform as a Product

أول صفة تذكرها ورقة CNCF للمنصات هي أن تعامل المنصة كمنتج Platform as a Product، أي أن مستخدميها هم المطورون في شركتك، وأنها تصمم وتتطور بحسب ما يحتاجونه فعلاً كما يصمم أي منتج تجاري بحسب عملائه. وهذا يعني عملياً عدة أشياء:

- **البحث مع المستخدمين User Research:** قبل أن تبني أي قدرة اسأل الفرق ما الذي يأخذ من وقتها أكثر، وغالباً سوف تجد أن المشكلة أبسط مما تظن، مثل بيئة اختبار تحتاج يومين لإعدادها.
- **التبني الطوعي Adoption:** المنصة الناجحة يستخدمها الناس لأنها أسهل، لا لأن المدير أمر بها، ولذلك فنسبة الفرق التي تستخدمها بإرادتها هي أحد أهم مقاييسها.
- **أصغر منصة ممكنة Thinnest Viable Platform:** مصطلح من كتاب Team Topologies يعني أن تبني أقل طبقة تعطي قيمة واضحة ثم تكبر منها، وقد تكون هذه الطبقة صفحة ويكي وقالب مستودع واحداً.
- **القياس:** تقترح DORA في صفحتها قياس أداء التسليم بمقاييسها المعروفة، ورضا المطورين باستبيانات بسيطة، ونسبة التبني والاستمرار، ونجاح المهمة، وتذكر أن أكثر قدرة في المنصة ارتبطت بتجربة جيدة للمستخدمين في بيانات 2025 هي أن تعطيه «ملاحظات واضحة عن نتيجة مهامه» Clear Feedback، أي أن يفهم ماذا حدث عندما يفشل شيء.

وتذكر DORA في الصفحة نفسها نتيجة من أبحاث 2024: استقلال المطور Developer Independence، أي قدرته على إنجاز مهامه دون الاعتماد على فريق آخر، ارتبط بتحسن 5% في الإنتاجية على مستوى الفريق والفرد. وتذكر أيضاً أن الأداء قد ينخفض مؤقتاً في بداية مشروع المنصة قبل أن يتحسن، وهذا متوقع لأن الفرق تنتقل من طريقة إلى أخرى.

## فريق المنصة في Team Topologies

كتاب [Team Topologies](https://teamtopologies.com/key-concepts?ref=arabroot.io) لمؤلفيه Matthew Skelton وManuel Pais، الذي صدر سنة 2019 وصدرت [طبعته الثانية](https://teamtopologies.com/book?ref=arabroot.io) في سبتمبر 2025، هو المرجع الذي تستند إليه أغلب كتابات هندسة المنصات، وقد شرحنا أنواع الفرق الأربعة فيه باختصار في [خارطة CTO لشركة SaaS ناشئة](https://arabroot.io/articles/%D8%AE%D8%A7%D8%B1%D8%B7%D8%A9-cto-%D9%84%D8%B4%D8%B1%D9%83%D8%A9-saas-%D9%86%D8%A7%D8%B4%D8%A6%D8%A9-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%81%D8%B1%D9%82/#team)، وما يهمنا هنا هو موقع فريق المنصة بينها وطريقة تعامله مع غيره. والصورة التالية تبين الأنواع الأربعة وطرق التعامل الثلاث:

![ثلاثة أشرطة صفراء على شكل أسهم: Stream-aligned team Orders وPayments وMobile app. تحتها شريط بنفسجي Platform team مكتوب فيه internal product: self-service CI/CD, environments, observability، ومنه ثلاثة أسهم إلى الأعلى مع شارة X-as-a-Service. وعلى اليمين صندوق أخضر Enabling team مكتوب فيه helps teams learn e.g. security, testing مع سهم متقطع إلى فريق Payments وشارة Facilitation. وعلى اليسار صندوق برتقالي Complicated subsystem team e.g. fraud model ملاصق لفريق Payments مع كلمة Collaboration. وفي الأسفل شرح لطرق التعامل الثلاث](https://arabroot.io/content/images/2026/10/platform-engineering-04-team-topologies.webp)

فريق المنصة يخدم الفرق الأخرى كخدمة X-as-a-Service، ولا يعمل كجزء من كل فريق

يعرف الموقع الرسمي للكتاب فريق المنصة بأنه تجميع لأنواع الفرق الأخرى يقدم منتجاً داخلياً جذاباً يسرع التسليم عند الفرق المرتبطة بمسار العمل Stream-aligned Teams، ويعرف ثلاث طرق للتعامل بين الفرق: التعاون Collaboration لفترة محددة لاكتشاف شيء جديد، والخدمة X-as-a-Service حيث يقدم فريق شيئاً ويستهلكه الآخر، والتيسير Facilitation حيث يساعد فريق فريقاً آخر ويعلمه. وفي الإعداد أعلاه لاحظ التالي:

- طريقة التعامل الطبيعية بين فريق المنصة وبقية الفرق هي X-as-a-Service، أي أن فريق الطلبات يستخدم خط النشر كما يستخدم خدمة سحابية، دون اجتماع ودون تذكرة.
- التعاون Collaboration مع فريق المنصة مناسب عند بناء قدرة جديدة، كأن يعمل فريق المنصة مع أول فريق يحتاج قاعدة بيانات مدارة حتى يفهم ما يحتاجه، ثم يعود التعامل إلى X-as-a-Service.
- فريق التمكين Enabling Team ليس فريق المنصة، فهو يعلم الفرق ويرحل، أما فريق المنصة فيبني منتجاً يبقى ويصونه.

## ورقة المنصات ونموذج النضج من CNCF

أصدرت مجموعة TAG App Delivery في CNCF وثيقتين يرجع إليهما كثير ممن يكتب عن الموضوع. الأولى هي [ورقة المنصات](https://tag-app-delivery.cncf.io/whitepapers/platforms/?ref=arabroot.io) وتذكر سبع صفات للمنصة الجيدة: أن تعامل كمنتج، وتجربة المستخدم User Experience، والتوثيق والتهيئة Documentation and Onboarding، والخدمة الذاتية Self-service، وتقليل الحمل الذهني على المستخدمين، وأن تكون اختيارية وقابلة للتركيب Optional and Composable، وآمنة افتراضياً Secure by Default. وتذكر الورقة أيضاً مجالات القدرات التي قد تقدمها المنصة، منها بوابات الويب، وال API وسطر الأوامر، وقوالب الطرق الممهدة، وأتمتة البناء والنشر، وبيئات التطوير، والمراقبة Observability، وخدمات البنية والبيانات والرسائل، والهوية والأسرار، وخدمات الأمن، ومخازن الحزم Artifacts.

والثانية هي [نموذج نضج هندسة المنصات Platform Engineering Maturity Model](https://tag-app-delivery.cncf.io/whitepapers/platform-eng-maturity-model/?ref=arabroot.io)، وفيه أربعة مستويات وخمسة جوانب، وهذا هو جدوله كما هو في الوثيقة مع ترجمة كل خانة:

| الجانب                                           | 1\. Provisional | 2\. Operational      | 3\. Scalable     | 4\. Optimizing            |
| ------------------------------------------------ | --------------- | -------------------- | ---------------- | ------------------------- |
| الاستثمار Investment: كيف يخصص الناس والمال؟     | تطوعي أو مؤقت   | فريق مخصص            | كمنتج            | منظومة ممكنة              |
| التبني Adoption: لماذا وكيف يستخدم الناس المنصة؟ | متقطع           | دفع خارجي بالتوجيهات | جذب ذاتي لقيمتها | مشاركة، والفرق تساهم فيها |
| الواجهات Interfaces: كيف تستهلك القدرات؟         | إجراءات خاصة    | أدوات قياسية         | حلول خدمة ذاتية  | خدمات متكاملة             |
| العمليات Operations: كيف تخطط وتصان؟             | عند الطلب       | متابعة مركزية        | تمكين مركزي      | خدمات مدارة               |
| القياس Measurement: كيف يجمع الرأي ويستفاد منه؟  | عشوائي          | جمع منتظم            | رؤى              | كمي ونوعي                 |

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

## الأدوات: Backstage وPort وHumanitec

قبل الأدوات تذكر أن المنصة نفسها تبنى غالباً من أدوات تعرفها: خط CI مثل [Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/)، ومستودع إعداد مع GitOps كما في [البيئات وGitOps للفرق](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/)، وأدوات البنية كشيفرة، وما نذكره هنا هو أدوات البوابة والتنسيق التي تجمع ذلك.

### Backstage

[Backstage](https://backstage.io/?ref=arabroot.io) إطار مفتوح المصدر لبناء بوابات المطورين بدأ في Spotify، وترخيصه [Apache-2.0](https://github.com/backstage/backstage/blob/master/LICENSE?ref=arabroot.io)، وحسب [صفحته في CNCF](https://www.cncf.io/projects/backstage/?ref=arabroot.io) قبل في CNCF في 8 سبتمبر 2020 ثم [انتقل إلى مستوى Incubating](https://www.cncf.io/blog/2022/03/15/backstage-project-joins-the-cncf-incubator/?ref=arabroot.io) في 15 مارس 2022، وما زال في هذا المستوى حتى كتابة المقال، وقد شرحنا مستويات المشاريع في [مشاريع CNCF](https://arabroot.io/articles/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-cncf-%D8%AE%D8%B1%D9%8A%D8%B7%D8%A9-%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-cloud-native/). وأهم ما فيه:

- **فهرس البرمجيات** [**Software Catalog**](https://backstage.io/docs/features/software-catalog/?ref=arabroot.io)**:** كل خدمة ومكتبة وموقع وخط بيانات يسجل بملف `catalog-info.yaml` في مستودعه، فيه المالك ودورة الحياة والعلاقات، فتعرف من يملك ماذا.
- **القوالب** [**Software Templates**](https://backstage.io/docs/features/software-templates/?ref=arabroot.io)**:** وهي التي تنفذ الطريق الممهد، فالمطور يملأ نموذجاً ويحصل على مستودع جاهز، وهي ما سوف نجربه بعد قليل.
- [**TechDocs**](https://backstage.io/docs/features/techdocs/?ref=arabroot.io)**:** توثيق يكتب بصيغة Markdown بجانب الكود ويعرض في البوابة.
- [**البحث Search**](https://backstage.io/docs/features/search/?ref=arabroot.io) عبر الفهرس والتوثيق، و**الإضافات Plugins** مثل [إضافة Kubernetes](https://backstage.io/docs/features/kubernetes/?ref=arabroot.io) التي تعرض حالة الخدمة في ال Cluster.

ولاحظ أن Backstage إطار وليس منتجاً جاهزاً، فالأمر `create-app` ينشئ لك تطبيق TypeScript كاملاً تملكه أنت وتبنيه وتحدثه، وكل إضافة تضاف بتعديل الكود، ولذلك فهو يحتاج من يصونه بشكل مستمر، وهذا سوف يظهر بوضوح في التجربة.

### Port

[Port](https://www.port.io/?ref=arabroot.io) منصة تجارية تقدم كخدمة سحابية SaaS، و[توثيقها](https://docs.port.io/?ref=arabroot.io) يصفها كبوابة Portal فيها فهرس Catalog وإجراءات ذاتية Self-service Actions، بينما تصف نفسها اليوم في صفحتها الرئيسية بأنها منصة لبناء دورة تطوير البرمجيات بالوكلاء Agentic SDLC وإدارتها. وفي [صفحة الأسعار](https://www.port.io/pricing?ref=arabroot.io) باقة مجانية حتى 15 مستخدماً، ثم باقات مدفوعة لكل مستخدم في الشهر. ونقطة قوتها أنك لا تبني الواجهة ولا تصونها، ونقطة ضعفها أن فهرس شركتك وبيانات خدماتها تعيش عند طرف خارجي.

### Humanitec

[Humanitec](https://humanitec.com/products/platform-orchestrator?ref=arabroot.io) شركة تجارية تركز على الجزء الخلفي من المنصة، فمنتجها الأساسي Platform Orchestrator يصف نفسه بأنه الواجهة الخلفية للمنصة التي توحد إدارة البنية وتفرض المعايير، وعندها بوابة خاصة بها أو تعمل مع بوابة أخرى. وهي أيضاً من بدأ مواصفات [Score](https://score.dev/?ref=arabroot.io)، وهي صيغة يصف بها المطور ما يحتاجه تطبيقه دون تفاصيل البنية، وهي اليوم مشروع في مستوى Sandbox في CNCF.

وقد يتساءل البعض: أيها أختار؟ والإجابة أن السؤال الأول ليس أي بوابة، وإنما هل عندك قدرات تستحق بوابة، فإذا كان عندك عدد قليل من الفرق وخط CI موحد، فملف README جيد وقالب مستودع في Gitea قد يكفيانك سنة كاملة.

## تجربة عملية: قالب في Backstage ينشئ خدمة في Gitea مع خط CI

في هذه التجربة نبني أصغر طريق ممهد ممكن: قالب في Backstage يسأل المطور عن اسم الخدمة، ثم ينشئ مستودعاً في Gitea فيه كود Node.js صغير مع اختبار وملف Workflow لخط CI على Gitea Actions، ثم يسجل الخدمة في فهرس Backstage، والصورة التالية تبين الخطوات:

![على اليمين حاسوب Developer fills the form بسهم إلى صندوق Backstage lab-plat-backstage فيه ثلاث خطوات مرقمة: 1 fetch:template و2 publish:gitea و4 catalog:register، وتحتها Software Catalog: inventory-api. من الخطوة 2 سهم أخضر create repo and push إلى صندوق Gitea lab-plat-gitea فيه مستودع platform/inventory-api مع catalog-info.yaml و.gitea/workflows/ci.yml، ومن Gitea سهم إلى الأسفل مع الرقم 3 push starts CI إلى صندوق Gitea Actions runner lab-plat-runner فيه npm test وعلامة صح. ومن Gitea سهم أزرق متقطع reads catalog-info.yaml إلى الخطوة 4. وفي صندوق أصفر all ports bound to 127.0.0.1 only :13000 UI :17007 API :13001 Gitea](https://arabroot.io/content/images/2026/10/platform-engineering-05-lab-flow.webp)

القالب ينشئ المستودع، والدفع الأول يشغل CI، ثم تسجل الخدمة في الفهرس

ونفذنا التجربة على Docker Engine 29.8.0 بثلاثة Containers تبدأ أسماؤها بـ `lab-plat-` على شبكة واحدة، وكل منفذ منشور مربوط بالعنوان `127.0.0.1` فقط، والإصدارات كما يلي: Gitea 28.1.0، وGitea Runner 5.0.0، وBackstage 1.55.0 من الأمر `@backstage/create-app@0.9.2`، وNode.js 24.21.0.

### الخطوة الأولى: Gitea ومؤسسة للمنصة

نبدأ بالشبكة وGitea بقاعدة SQLite، فهذه بيئة تجربة، أما التثبيت الكامل للإنتاج مع PostgreSQL والنسخ الاحتياطي فقد شرحناه في [دليل Gitea](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/):

```bash
docker network create lab-plat-net

docker run -d --name lab-plat-gitea --network lab-plat-net \
  -p 127.0.0.1:13001:3000 \
  -e GITEA__security__INSTALL_LOCK=true \
  -e GITEA__server__ROOT_URL=http://lab-plat-gitea:3000/ \
  -e GITEA__server__DOMAIN=lab-plat-gitea \
  -e GITEA__database__DB_TYPE=sqlite3 \
  -e GITEA__actions__ENABLED=true \
  gitea/gitea:28.1.0

docker exec -u git lab-plat-gitea gitea admin user create \
  --username platadmin --password 'LabPlat-2026!x' \
  --email platadmin@example.test --admin --must-change-password=false
docker exec -u git lab-plat-gitea gitea --version
```

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

```bash
New user 'platadmin' has been successfully created!
gitea version 28.1.0 built with go1.27.1 : bindata, timetzdata,
```

بعد ذلك ننشئ مؤسسة Organization باسم `platform` تعيش فيها المستودعات التي تنشئها المنصة، ثم توكن Token يستخدمه Backstage، والسبب في المؤسسة أن إجراء Backstage الخاص بـ Gitea لا ينشئ المستودع إلا داخل مؤسسة كما سوف نرى في حدود التجربة:

```bash
curl -s -u 'platadmin:LabPlat-2026!x' -X POST http://127.0.0.1:13001/api/v1/orgs \
  -H 'Content-Type: application/json' -d '{"username":"platform","visibility":"public"}'

GITEA_TOKEN=$(curl -s -u 'platadmin:LabPlat-2026!x' -X POST \
  http://127.0.0.1:13001/api/v1/users/platadmin/tokens \
  -H 'Content-Type: application/json' \
  -d '{"name":"backstage","scopes":["write:organization","write:repository","read:user"]}' | jq -r .sha1)
```

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

### الخطوة الثانية: Runner لخط CI

نسجل Runner في وضع المضيف Host Mode، أي أن خطوات الخط تعمل داخل Container ال Runner نفسه دون أن ينشئ Containers أخرى، وهذا اختصار للتجربة فقط، أما في الإنتاج فاستخدم وضع Docker كما في [دليل Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/)، والسبب أن وضع المضيف يشغل كود أي مستودع على ال Runner نفسه دون عزل:

```bash
RUNNER_TOKEN=$(docker exec -u git lab-plat-gitea gitea actions generate-runner-token)

docker run -d --name lab-plat-runner --network lab-plat-net \
  -e GITEA_INSTANCE_URL=http://lab-plat-gitea:3000 \
  -e GITEA_RUNNER_REGISTRATION_TOKEN=$RUNNER_TOKEN \
  -e GITEA_RUNNER_NAME=lab-plat-runner \
  -e GITEA_RUNNER_LABELS=ubuntu-latest:host \
  gitea/runner:5.0.0

docker exec lab-plat-runner sh -c 'apk add --no-cache nodejs npm'
docker logs lab-plat-runner
```

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

```bash
level=info msg="Registering runner, arch=amd64, os=linux, version=v5.0.0."
level=debug msg="Successfully pinged the Gitea instance server"
level=info msg="Runner registered successfully."
SUCCESS
time="2026-10-09T13:24:41Z" level=info msg="Starting runner daemon"
time="2026-10-09T13:24:41Z" level=info msg="runner: lab-plat-runner, with version: v5.0.0, with labels: [ubuntu-latest], declare successfully"
```

ولاحظ أن Docker Image الخاصة بال Runner مبنية على Alpine ولا تحتوي Node.js، ولذلك أضفنا `nodejs` لأن الإجراء `actions/checkout` مكتوب بـ JavaScript، و`npm` لأن الخط يشغل `npm test`، وسوف ترى في قسم الأخطاء ماذا حدث عندما نسينا `npm`.

### الخطوة الثالثة: إنشاء تطبيق Backstage

نشغل Container من Node.js 24، لأن التطبيق الذي ينشئه `create-app` يطلب في ملف `package.json` الإصدار 22 أو 24، وننشر منفذي الواجهة وال API على `127.0.0.1`:

```bash
docker run -d --name lab-plat-backstage --network lab-plat-net \
  -p 127.0.0.1:13000:3000 -p 127.0.0.1:17007:7007 \
  -w /work node:24.21.0-bookworm sleep infinity

docker exec -it -e GITEA_TOKEN=$GITEA_TOKEN lab-plat-backstage bash
corepack enable
npx @backstage/create-app@0.9.2
```

يسأل الأمر عن اسم التطبيق، وأدخلنا `platform-lab`، ثم ينسخ الملفات ويشغل `yarn install`، وهنا فشل التثبيت في تجربتنا، وأعدنا التشغيل في مجلد آخر باسم `verify-app` لنتأكد أن الخطأ يتكرر، وهذا هو المخرج من المرة الثانية:

```bash
 Installing dependencies:
➤ YN0000: · Yarn 4.13.0
➤ YN0000: ┌ Resolution step
➤ YN0001: │ Error: got@patch:got@npm%3A11.8.2#~/.yarn/patches/got-npm-11.8.2-c1eb105458.patch: ENOENT: no such file or directory, open '/work/verify/verify-app/.yarn/patches/got-npm-11.8.2-c1eb105458.patch'
➤ YN0000: └ Completed in 25s 966ms
➤ YN0000: · Failed with errors in 26s 10ms
  executing     yarn install ✖

Error: Could not execute command yarn install
```

وبتتبع الخطأ وجدنا أن الحزمة `@yarnpkg/core` في إصدارها 4.9.2 المنشور في 24 سبتمبر 2026 تعتمد على `got` بصيغة `patch:` تشير إلى ملف موجود في مستودع Yarn نفسه وليس في الحزمة، ويمكنك أن ترى ذلك بنفسك بالأمر التالي:

```bash
npm view @yarnpkg/core@4.9.2 dependencies.got
npm view @yarnpkg/core@4.9.1 dependencies.got
```

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

```bash
patch:got@npm%3A11.8.2#~/.yarn/patches/got-npm-11.8.2-c1eb105458.patch
^11.7.0
```

والحل أن نثبت الإصدار السابق في قسم `resolutions` داخل `package.json` في مجلد التطبيق، ثم نعيد التثبيت:

```json
  "resolutions": {
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@yarnpkg/core": "4.9.1"
  },
```

```bash
cd /work/platform-lab
yarn install
```

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

بعد ذلك نضيف الوحدة Module التي تحتوي الإجراء `publish:gitea`، لأن التطبيق الجديد يأتي بإجراءات GitHub فقط:

```bash
yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-gitea
```

ثم نضيف هذا السطر في `packages/backend/src/index.ts` بعد سطر وحدة GitHub:

```typescript
backend.add(import('@backstage/plugin-scaffolder-backend-module-gitea'));
```

### الخطوة الرابعة: ربط Backstage بـ Gitea

نضع إعداداتنا في ملف `app-config.local.yaml` بجانب `app-config.yaml`، وهو ملف يقرؤه Backstage فوق الإعداد الأساسي عند التشغيل للتطوير:

```yaml
app:
  baseUrl: http://localhost:13000
  listen:
    host: 0.0.0.0
    port: 3000

backend:
  baseUrl: http://localhost:17007
  listen:
    host: 0.0.0.0
    port: 7007
  cors:
    origin: http://localhost:13000

integrations:
  gitea:
    - host: lab-plat-gitea:3000
      baseUrl: http://lab-plat-gitea:3000
      username: platadmin
      password: ${GITEA_TOKEN}

catalog:
  locations:
    - type: file
      target: ../../examples/entities.yaml
    - type: file
      target: ../../examples/org.yaml
      rules:
        - allow: [User, Group]
    - type: file
      target: ../../templates/node-service/template.yaml
      rules:
        - allow: [Template]
```

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

- `baseUrl` هو العنوان الذي يفتحه المتصفح من جهازك عبر المنفذ المنشور، أما `listen` فهو المنفذ داخل ال Container، ولذلك يختلفان.
- في قسم `integrations.gitea` كتبنا `baseUrl` صراحة، والسبب أن [ربط Gitea](https://backstage.io/docs/integrations/gitea/locations?ref=arabroot.io) يفترض `https://` أمام اسم المضيف إذا لم تكتبه، وبيئة التجربة تعمل على HTTP داخل شبكة Docker.
- الحقل `password` يقبل التوكن بدلاً من كلمة المرور، والقيمة تقرأ من متغير البيئة `GITEA_TOKEN` فلا تكتب التوكن في الملف.
- القوائم في إعداد Backstage لا تدمج مع الملف الأساسي وإنما تستبدله، ولذلك أعدنا كتابة مواقع الأمثلة مع موقع القالب.

### الخطوة الخامسة: القالب

القالب ملف `templates/node-service/template.yaml`، ومعه مجلد `skeleton` فيه الملفات التي تنسخ إلى المستودع الجديد:

```yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: node-service
  title: Node.js service with CI
  description: New repository in Gitea with a small Node.js service, tests and a Gitea Actions workflow
spec:
  owner: group:guests
  type: service

  parameters:
    - title: Service details
      required: [repoUrl, owner]
      properties:
        repoUrl:
          title: Repository
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts:
              - lab-plat-gitea:3000
            allowedOwners:
              - platform
        description:
          title: Description
          type: string
          default: A new service
        owner:
          title: Owner team
          type: string
          default: group:guests

  steps:
    - id: fetch
      name: Fetch skeleton
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.repoUrl | parseRepoUrl | pick('repo') }}
          description: ${{ parameters.description }}
          owner: ${{ parameters.owner }}

    - id: publish
      name: Create the repository in Gitea
      action: publish:gitea
      input:
        repoUrl: ${{ parameters.repoUrl }}
        description: ${{ parameters.description }}
        defaultBranch: main
        repoVisibility: public

    - id: register
      name: Register in the catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
        catalogInfoPath: catalog-info.yaml

  output:
    links:
      - title: Repository
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}
```

وفي مجلد `skeleton` ملف الخط `.gitea/workflows/ci.yml`، وهو الذي يجعل كل خدمة جديدة تبدأ باختبارات تعمل مع أول `push`:

```yaml
name: ci
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: node --version
      - run: npm test
```

وملف التسجيل في الفهرس `catalog-info.yaml`:

```yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: ${{ values.name }}
  description: ${{ values.description }}
spec:
  type: service
  lifecycle: experimental
  owner: ${{ values.owner }}
```

وبجانبهما ملف `package.json` فيه السكربت `"test": "node --test"`، وملف `index.js` فيه دالة `health()` وسيرفر HTTP صغير، وملف `test/health.test.js` فيه اختبار واحد يتأكد أن الدالة ترجع `ok`، وملف `README.md`. وفي الإعداد أعلاه لاحظ التالي:

- الخطوة `fetch:template` تمر على كل ملفات المجلد وتستبدل `${{ values.name }}` وأمثاله بقيم النموذج.
- الخطوة `publish:gitea` تنشئ المستودع عبر API الخاصة بـ Gitea ثم تدفع الملفات بفرع `main`.
- الخطوة `catalog:register` تقرأ `catalog-info.yaml` من المستودع الجديد وتسجله في الفهرس، ولاحظ أننا كتبنا المسار `catalog-info.yaml` بدون `/` في بدايته، والسبب سوف تراه في قسم الأخطاء.

الآن نشغل Backstage بوضع التطوير من داخل ال Container، والمتغير `GITEA_TOKEN` وصل إليه من الأمر `docker exec` في الخطوة الثالثة:

```bash
cd /work/platform-lab
yarn start
```

ومن سطور المخرج لاحظ هذا السطر الذي يؤكد أن الإجراء `publish:gitea` أصبح متاحاً:

```bash
scaffolder info Starting scaffolder with the following actions enabled publish:gitea, notification:send, github:actions:dispatch, github:autolinks:create, github:deployKey:create, github:environment:create, github:issues:label, github:issues:create, github:repo:create, github:repo:push, github:webhook, publish:github, publish:github:pull-request, github:pages:enable, github:branch-protection:create, fetch:plain, fetch:plain:file, fetch:template, fetch:template:file, debug:log, debug:wait, catalog:register, catalog:fetch, catalog:write, fs:delete, fs:rename, fs:readdir
```

### الخطوة السادسة: تشغيل القالب

نفتح `http://localhost:13000` وندخل كضيف Guest، ثم من قائمة Create نختار القالب، والصورة التالية تبين النموذج الذي يراه المطور:

![لقطة من واجهة Backstage في صفحة Create ثم Templates ثم node-service، وفيها بطاقة بعنوان Node.js service with CI ووصف New repository in Gitea with a small Node.js service, tests and a Gitea Actions workflow، وخطوتان Service details وReview، وتحت عنوان Repository حقل Host فيه lab-plat-gitea:3000 وحقل Owner Available فيه platform وحقل Repository فارغ وحقل Description فيه A new service](https://arabroot.io/content/images/2026/10/platform-engineering-06-template-form.webp)

المطور يختار اسم المستودع فقط، والمضيف والمؤسسة محددان في القالب

وكتبنا اسم المستودع `inventory-api` والوصف `Inventory API`، ويمكنك أن تشغل القالب نفسه من ال API بدون الواجهة، وهذا مفيد لأن المنصة يجب أن تعمل من سطر الأوامر أيضاً:

```bash
TOKEN=$(curl -s "http://127.0.0.1:17007/api/auth/guest/refresh?env=development" \
  -H 'X-Requested-With: XMLHttpRequest' | jq -r .backstageIdentity.token)

curl -s -X POST http://127.0.0.1:17007/api/scaffolder/v2/tasks \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"templateRef":"template:default/node-service","values":{"repoUrl":"lab-plat-gitea:3000?owner=platform&repo=inventory-api","description":"Inventory API","owner":"group:guests"}}'
```

ويرجع الطلب رقم المهمة `id`، ومن الرابط `/api/scaffolder/v2/tasks/<id>/events` نقرأ سجل التنفيذ، والمخرج سوف يكون كما يلي:

```bash
Starting up task with 3 steps
Beginning step Fetch skeleton
info: Fetching template content from remote URL
info: Listing files and directories in template
info: Processing 9 template files/directories with input values {"description":"Inventory API","name":"inventory-api","owner":"group:guests"}
info: Writing directory .gitea/ to template output path.
info: Writing file README.md to template output path with mode 33261.
info: Writing file catalog-info.yaml to template output path with mode 33261.
info: Writing file index.js to template output path with mode 33261.
info: Writing file package.json to template output path with mode 33261.
info: Writing directory test/ to template output path.
info: Writing directory .gitea/workflows/ to template output path.
info: Writing file test/health.test.js to template output path with mode 33261.
info: Writing file .gitea/workflows/ci.yml to template output path with mode 33261.
info: Template result written to /tmp/5153ba89-c099-494e-8343-1868ea831db0
Finished step Fetch skeleton
Beginning step Create the repository in Gitea
info: Init git repository {dir=/tmp/5153ba89-c099-494e-8343-1868ea831db0}
info: Adding file {dir=/tmp/5153ba89-c099-494e-8343-1868ea831db0,filepath=.}
info: Committing file to repo {dir=/tmp/5153ba89-c099-494e-8343-1868ea831db0,message=initial commit

Change-Id: I7ae37a3052b65adcfc49a421373d5614c17ea37f}
info: Pushing directory to remote {dir=/tmp/5153ba89-c099-494e-8343-1868ea831db0,remote=origin}
Finished step Create the repository in Gitea
Beginning step Register in the catalog
info: Registering http://lab-plat-gitea:3000/platform/inventory-api/src/branch/main/catalog-info.yaml in the catalog
Finished step Register in the catalog
Run completed with status: completed
```

ومع وصول الدفع الأول إلى Gitea بدأ خط CI على ال Runner، ومن سجل الخطوة `npm test` في Gitea:

```bash
> inventory-api@0.1.0 test
> node --test

✔ health returns ok (2.354146ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
```

والصورة التالية من صفحة Actions في المستودع الجديد:

![لقطة من Gitea 28.1.0 لمستودع platform/inventory-api في تبويب Actions، وفيها تشغيل initial commit رقم 1 بعلامة نجاح، و Triggered via push بواسطة platadmin على فرع main، وStatus Success، وTotal duration 5s، وملف ci.yml on: push بمهمة واحدة test نجحت](https://arabroot.io/content/images/2026/10/platform-engineering-07-gitea-actions-run.webp)

أول commit أنشأه القالب شغل خط CI ونجح خلال خمس ثوان

وأخيراً أصبحت الخدمة في الفهرس بمالكها، ومن ال API:

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:17007/api/catalog/entities/by-name/component/default/inventory-api
```

ومن المخرج هذه هي الحقول المهمة:

```json
{
 "kind": "Component",
 "name": "inventory-api",
 "annotations": {
  "backstage.io/managed-by-location": "url:http://lab-plat-gitea:3000/platform/inventory-api/src/branch/main/catalog-info.yaml",
  "backstage.io/managed-by-origin-location": "url:http://lab-plat-gitea:3000/platform/inventory-api/src/branch/main/catalog-info.yaml",
  "backstage.io/view-url": "http://lab-plat-gitea:3000/platform/inventory-api/src/branch/main/catalog-info.yaml",
  "backstage.io/edit-url": "http://lab-plat-gitea:3000/platform/inventory-api/_edit/main/catalog-info.yaml",
  "backstage.io/source-location": "url:http://lab-plat-gitea:3000/platform/inventory-api/src/branch/main/"
 },
 "spec": {
  "type": "service",
  "lifecycle": "experimental",
  "owner": "group:guests"
 }
}
```

![لقطة من Backstage لصفحة المكون inventory-api من نوع Component service، Lifecycle experimental وOwner guests، مع تبويبات Overview وTechDocs وAPIs، وأقسام Provided APIs وConsumed APIs وDepends on components، وبطاقة About فيها VIEW SOURCE وVIEW TECHDOCS والوصف Inventory API والمالك guests والنوع service](https://arabroot.io/content/images/2026/10/platform-engineering-08-catalog-entity.webp)

الخدمة الجديدة في الفهرس، ورابط View Source يفتح مستودعها في Gitea

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

### ماذا يحدث عندما يخطئ شيء؟

الأخطاء التالية كلها ظهرت لنا فعلاً أثناء التجربة، وهي أكثر ما سوف تقابله في أول أسبوع:

**المسار المطلق في `catalogInfoPath`.** في المحاولة الأولى كتبنا `catalogInfoPath: /catalog-info.yaml` كما في أمثلة Backstage لمزودات أخرى، فأنشئ المستودع وفشل التسجيل:

```bash
Beginning step Register in the catalog
info: Registering http://lab-plat-gitea:3000/platform/orders-api/src/branch/catalog-info.yaml in the catalog
ResponseError: Request failed with 400 Bad Request
```

لاحظ أن اسم الفرع `main` سقط من الرابط، والسبب أن ربط Gitea في Backstage يستخدم دالة عامة لحل المسارات المطلقة لا تفهم شكل روابط Gitea التي فيها `/src/branch/main/`، أما المسار النسبي `catalog-info.yaml` فيضاف إلى نهاية الرابط كما هو، ولذلك عمل.

**تشغيل القالب باسم مستودع موجود.** عندما شغلنا القالب مرة أخرى باسم `orders-api` الذي أنشئ في المحاولة الأولى:

```bash
Beginning step Create the repository in Gitea
Error: Unable to create repository, 409 Conflict, {"message":"The repository with the same name already exists.","url":"http://lab-plat-gitea:3000/api/swagger"}
Skipping step register because a previous step failed
Run completed with status: failed
```

ولاحظ أن الفشل في منتصف القالب يترك ما أنشئ قبله، فالمستودع من المحاولة الأولى بقي في Gitea دون تسجيل في الفهرس، وعليك أن تحذفه أو تسجله يدوياً، ولا يوجد تراجع آلي.

**مالك هو مستخدم وليس مؤسسة.** أرسلنا من ال API طلباً بالمالك `platadmin`، وهو مستخدم:

```bash
Beginning step Create the repository in Gitea
Error: Organization platadmin do not exist. Please create it first !
Skipping step register because a previous step failed
```

ولاحظ أمراً مهماً هنا: القالب يحدد `allowedOwners: [platform]`، ومع ذلك وصل الطلب إلى الخطوة، والسبب أن `ui:options` قيود على النموذج في الواجهة فقط، ولا يتحقق منها ال Backend، فإذا أردت منع الفرق من الكتابة في مؤسسات معينة فاجعل ذلك في صلاحيات التوكن في Gitea، لا في القالب.

**بيئة ال Runner ناقصة.** قبل أن نضيف `npm` إلى ال Runner فشل خط CI في أول خدمة رغم أن القالب سليم:

```bash
npm test
shell: bash -e {0}
/root/.cache/act/8f487d9d3e96a9a9/act/workflow/2.sh: line 2: npm: command not found
##[error]Process completed with exit code 127.
```

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

### حدود ربط Backstage مع Gitea

بعد التجربة وقراءة الكود المصدري لوحدة [scaffolder-backend-module-gitea](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitea?ref=arabroot.io) بإصدارها 0.2.25، هذه هي الحدود التي يجب أن تعرفها قبل أن تختار Gitea مع Backstage:

- **المستودعات داخل مؤسسة فقط:** الإجراء يتحقق من وجود المؤسسة ثم ينشئ المستودع عبر واجهة المؤسسات، وتعليق في الكود يذكر أن إنشاء المستودع في حساب المستخدم ممكن في Gitea ولكنه غير مدعوم حالياً.
- **إجراء واحد فقط:** قائمة الإجراءات في المخرج أعلاه فيها `publish:gitea` وحده لـ Gitea، بينما لـ GitHub أكثر من عشرة إجراءات، مثل حماية الفروع Branch Protection وإنشاء البيئات و Webhooks، فهذه في Gitea تحتاج إجراء تكتبه بنفسك Custom Action أو خطوة يدوية بعد القالب.
- **HTTPS افتراضياً:** بدون `baseUrl` يفترض الربط `https://`، و[ملف الوحدة](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitea?ref=arabroot.io) ينبه إلى أن Gitea يجب أن تكون بشهادة موثوقة، أو أن تضيف شهادة سلطتك الداخلية عبر `NODE_EXTRA_CA_CERTS`.
- **المسار المطلق في التسجيل:** كما رأيت في القسم السابق، استخدم مساراً نسبياً في `catalogInfoPath`.
- **الاكتشاف التلقائي موجود ولكن لكل مؤسسة:** هناك وحدة [Gitea Discovery](https://backstage.io/docs/integrations/gitea/discovery?ref=arabroot.io) تقرأ ملف `catalog-info.yaml` من المجلد الرئيسي لكل مستودع في المؤسسة، ولكل مؤسسة إعداد مستقل، ولم نجربها في هذا المقال.

وإذا كانت هذه الحدود تمنعك، فالطريق البسيط البديل هو ميزة القوالب في Gitea نفسها: تجعل مستودعاً ما [مستودع قالب Template Repository](https://docs.gitea.com/usage/template-repositories?ref=arabroot.io) من إعداداته، فيحصل كل مستودع جديد منه على ملف الخط نفسه، ويستطيع ملف `.gitea/template` أن يستبدل متغيرات مثل اسم المستودع داخل الملفات، ثم تسجل الخدمات في الفهرس بملف `catalog-info.yaml` وقت الحاجة، وهذا أقرب إلى فكرة أصغر منصة ممكنة.

### التنظيف

نحذف ما أنشأناه فقط، بأسمائه:

```bash
docker rm -f lab-plat-backstage lab-plat-runner lab-plat-gitea
docker network rm lab-plat-net
```

## متى لا تحتاج شركتك فريق منصة؟

بعد كل ما سبق قد يبدو أن كل شركة تحتاج منصة، والواقع غير ذلك، فلا تبدأ فريق منصة في الحالات التالية:

- **فريق واحد أو فريقان:** إذا كان كل المطورين يعرفون بعضهم ويعملون على منتج واحد، فلا يوجد «مستخدمون داخليون» تبني لهم منتجاً، والسبب أن تكلفة التواصل المباشر أقل من تكلفة بناء منصة وصيانتها، وقد شرحنا أثر حجم الفريق في [هل يسلم الفريق الأكبر أسرع؟](https://arabroot.io/articles/%D9%87%D9%84-%D9%8A%D8%B3%D9%84%D9%85-%D8%A7%D9%84%D9%81%D8%B1%D9%8A%D9%82-%D8%A7%D9%84%D8%A7%D9%83%D8%A8%D8%B1-%D8%A7%D8%B3%D8%B1%D8%B9-%D9%82%D8%A7%D9%86%D9%88%D9%86-brooks/).
- **تطبيق واحد على سيرفر أو اثنين:** خط CI واحد مع Docker Compose وخطوات تراجع مجربة يكفي، كما ذكرنا في [هل تحتاج Kubernetes فعلاً؟](https://arabroot.io/articles/%D9%87%D9%84-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC-kubernetes-%D9%81%D8%B9%D9%84%D8%A7/).
- **منصة جاهزة تكفيك:** إذا كانت خدمة PaaS أو أداة مثل [Coolify](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-coolify-%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA/) أو [Dokploy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-dokploy-%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA/) تعطيك النشر والبيئات، فهي منصتك فعلاً، وفريق داخلي يعيد بناءها هدر.
- **لا أحد يشتكي:** إذا لم تجد في الحديث مع الفرق مشكلة متكررة في البنية أو النشر، فلا تبن حلاً لمشكلة غير موجودة.

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

## أخطاء شائعة

تذكر صفحة DORA عن هندسة المنصات خمسة أخطاء، ونضيف إليها ما رأيناه:

- **ابنها وسوف يأتون Build It and They Will Come:** بناء المنصة على افتراضات دون الحديث مع المستخدمين، وهو أسرع طريق لمنصة لا يستخدمها أحد.
- **البرج العاجي Ivory Tower:** فريق مركزي يفرض معايير صارمة من الأعلى دون تعاون مع الفرق.
- **فخ التذاكر Ticket-ops:** إعادة تسمية فريق التشغيل إلى فريق منصة مع بقاء العمل كما هو تذاكر تنفذ باليد.
- **الإطلاق الكبير Big Bang:** محاولة بناء منصة شاملة قبل إطلاق أي شيء، والأفضل قدرة واحدة صغيرة يستخدمها فريق واحد ثم التوسع.
- **القفص الذهبي Golden Cage:** طريق واحد إلزامي للجميع لا يناسب فرقاً مختلفة مثل علم البيانات وتطبيقات الجوال.
- **البدء بالبوابة:** تثبيت Backstage قبل أن توجد أي قدرة ذاتية تحته، فتحصل على فهرس جميل وطابور التذاكر نفسه.
- **نسيان أن Backstage كود تملكه:** كما رأيت في التجربة، فهو تطبيق TypeScript يحتاج تحديثاً وصيانة مستمرين، فإذا لم يكن عندك من يصونه فاختر شيئاً أبسط.

## تدريب عملي

- أعد تشغيل التجربة، وأضف إلى القالب حقلاً لاختيار نسخة Node.js، واستخدمه في ملف الخط مع `actions/setup-node`.
- جرب الطريق البديل: اجعل مستودعاً في Gitea قالباً Template Repository، وأنشئ منه مستودعاً جديداً، وقارن بين الطريقتين في عدد الخطوات وما يحتاج صيانة.

## الخلاصة

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

- هندسة المنصات تحل ثلاث مشاكل: الحمل الذهني على فرق المنتج، والتشغيل عبر التذاكر، والبنية التي يبنيها كل فريق بطريقته، وذلك بقدرات يستخدمها المطور بنفسه.
- المنصة Internal Developer Platform هي القدرات، والبوابة Internal Developer Portal واجهة اختيارية فوقها، فلا تبدأ بالبوابة.
- الطريق الممهد Golden Path طريق «صاحب رأي ومدعوم» يجعل الخيار الصحيح هو الأسهل، ويبقى الخروج عنه ممكناً مع تحمل دعمه.
- المنصة منتج مستخدموه المطورون: ابدأ بأصغر منصة ممكنة، وقس التبني والرضا، وتعامل مع الفرق كخدمة X-as-a-Service كما في Team Topologies.
- نموذج النضج من CNCF يقول صراحة إن أعلى مستوى ليس هدفاً في ذاته.
- Backstage إطار مفتوح المصدر بترخيص Apache-2.0 في مستوى Incubating في CNCF، وPort وHumanitec خياران تجاريان، وربط Backstage مع Gitea يعمل ولكن بإجراء واحد وداخل مؤسسة فقط، مع انتباه للمسار النسبي في التسجيل.
- الشركة الصغيرة لا تحتاج فريق منصة، ويكفيها قالب مستودع وخط CI موحد وصفحة توثيق.

وفي المقال التالي من هذه المرحلة نشرح الفرق بين DevOps وSRE وهندسة المنصات، وكيف تتداخل، وشكل الفريق المناسب لكل حجم شركة.

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

- أكتوبر 2026: كتابة المقال، ومراجعة ورقة المنصات ونموذج النضج من CNCF TAG App Delivery، وصفحة DORA عن هندسة المنصات، والموقع الرسمي لكتاب Team Topologies، ومقال Spotify عن الطرق الممهدة، وصفحة Backstage في CNCF وترخيصه وتوثيق ربط Gitea والكود المصدري لوحدة publish:gitea، وصفحات Port وHumanitec وScore، وتنفيذ التجربة العملية على Docker Engine 29.8.0 مع Gitea 28.1.0 وGitea Runner 5.0.0 وBackstage 1.55.0 (create-app 0.9.2، scaffolder-backend 4.2.0، scaffolder-backend-module-gitea 0.2.25) وNode.js 24.21.0.