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

# تحليلات المنتج Product Analytics مع PostHog على سيرفرك
- URL: https://arabroot.io/articles/تحليلات-المنتج-product-analytics-مع-posthog-على-سيرفرك/
- Published: 2026-10-25T05:15:00.000Z
- Updated: 2026-10-25T17:01:26.000Z
- Description: نشغل PostHog على سيرفرنا ونتتبع المستخدم من أول زيارة إلى أول فاتورة، ونقيس ما يحتاجه من ذاكرة ووقت إقلاع.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, التسويق والمبيعات, الاستضافة الذاتية, PostHog

في [مقارنة أدوات تحليلات الموقع](https://arabroot.io/articles/%D8%AA%D8%AD%D9%84%D9%8A%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%AF%D9%88%D9%86-google-analytics-%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%B9%D9%85%D9%84%D9%8A%D8%A9/) عرفنا كم زائراً دخل الموقع ومن أين جاء، وفي [قياس التسويق](https://arabroot.io/articles/%D9%82%D9%8A%D8%A7%D8%B3-%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D9%82-%D8%B1%D9%88%D8%A7%D8%A8%D8%B7-utm-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D9%86%D8%A7%D8%AF-attribution/) ربطنا الزيارة بالحملة التي جلبتها، ولكن السؤال الذي يهم صاحب المنتج يبدأ بعد ذلك: من هذا الزائر سجل فعلاً؟ ومن سجل هل أنشأ أول فاتورة؟ ومن أنشأ الفاتورة هل عاد في الأسبوع التالي؟ وهذه أسئلة تحليلات المنتج Product Analytics، وأشهر أداة مفتوحة المصدر لها هي PostHog.

المواضيع في مرحلة التحليلات:

- [قياس التسويق: روابط UTM والإسناد Attribution](https://arabroot.io/articles/%D9%82%D9%8A%D8%A7%D8%B3-%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D9%82-%D8%B1%D9%88%D8%A7%D8%A8%D8%B7-utm-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D9%86%D8%A7%D8%AF-attribution/)
- [تحليلات الموقع دون Google Analytics: مقارنة عملية](https://arabroot.io/articles/%D8%AA%D8%AD%D9%84%D9%8A%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%AF%D9%88%D9%86-google-analytics-%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%B9%D9%85%D9%84%D9%8A%D8%A9/)
- تحليلات المنتج مع PostHog على سيرفرك (الموضوع الحالي)
- [اختبارات A/B بدقة مع GrowthBook على سيرفرك](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-ab-%D8%A8%D8%AF%D9%82%D8%A9-%D9%85%D8%B9-growthbook-%D8%B9%D9%84%D9%89-%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D9%83/)

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

- نقرأ ما تقوله PostHog نفسها عن الاستضافة الذاتية Self-hosting اليوم: الدعم، والإصدارات، والحدود.
- نشغل نسخة Hobby الرسمية في Docker مثبتة على Commit محدد، ونقيس الذاكرة والمعالج والقرص ووقت الإقلاع.
- نربط تطبيق ويب صغيراً بالعربية، ونرسل منه الزيارات Pageviews والأحداث المخصصة Custom Events، ونربط الزائر بحسابه بالدالة `identify`.
- نبني قمع التحويل Funnel وتقرير الاحتفاظ Retention، ونشاهد تسجيل جلسة Session Replay، ونفعل ميزة لنصف المستخدمين بـ Feature Flag.
- نختبر أسماء أحداث وخصائص بالعربية، ونرى كيف تظهر في الواجهة.

والصورة التالية تبين طريق الحدث من المتصفح إلى التقارير:

![المتصفح فيه التطبيق مع posthog-js يرسل إلى Caddy، وCaddy يوجه /e/ و/i/ إلى خدمة capture، و/s/ إلى replay-capture، و/flags إلى feature-flags، وcapture وreplay-capture يكتبان في Kafka (Redpanda)، وخدمات ingestion المكتوبة بـ Node.js تقرأ من Kafka وتكتب الأحداث في ClickHouse والأشخاص في PostgreSQL والتسجيلات في SeaweedFS، وتطبيق web المكتوب بـ Django يقرأ منها ليعرض التقارير، وفي الأسفل قائمة بخدمات أخرى في نفس الملف، والمجموع 39 خدمة](https://arabroot.io/content/images/2026/10/posthog-lab-01-lab-map.webp)

الحدث يمر بالـ Reverse Proxy ثم خدمة الاستقبال ثم الطابور ثم قواعد البيانات، وكل ذلك على سيرفر واحد

## المتطلبات

- سيرفر Ubuntu، و[صفحة الاستضافة الذاتية](https://posthog.com/docs/self-host?ref=arabroot.io) تطلب ما يعادل 4 vCPU و16 GB من الذاكرة RAM وأكثر من 30 GB من التخزين، وسوف ترى في قسم [الموارد](#resources) لماذا لا تنزل عن ذلك.
- نطاق فرعي Subdomain مثل `analytics.example.com` عليه سجل `A` يشير إلى السيرفر، والمنفذان 80 و443 مفتوحان، لأن الـ Caddy المدمج يطلب شهادة Let's Encrypt بنفسه.
- حوالي 30 GB للـ Images وحدها، فقد بلغ مجموع الـ Images في تجربتنا 28.1 GB بعد فك الضغط.
- تطبيق ويب تريد قياسه، وقد استخدمنا صفحات HTML بسيطة لمتجر فواتير تجريبي اسمه «فاتورتي».

## ماذا تقول PostHog عن الاستضافة الذاتية Self-hosting؟

قبل أن تشغل أي أمر، اقرأ ما تقوله الشركة نفسها، لأنه يحدد هل هذا الخيار يناسبك أصلاً. في [صفحة الاستضافة الذاتية](https://posthog.com/docs/self-host?ref=arabroot.io) و[صفحة إخلاء المسؤولية](https://posthog.com/docs/self-host/open-source/disclaimer?ref=arabroot.io) كما قرأناهما في 25 أكتوبر 2026:

- **الدعم**: الاستضافة الذاتية غير مدعومة رسمياً Officially Unsupported، ولا توجد خطط دعم مدفوعة لها، والشركة لا تقدم أي ضمانات، وتقول إنها لا تستطيع المساعدة في استعادة البيانات إذا فقدت.
- **الإصدارات**: لا توجد إصدارات مرقمة Tagged Releases للاستضافة الذاتية، وكل تغيير ومنه الإصلاحات الأمنية يخرج باستمرار من فرع `master` إلى السحابة وإلى آخر Docker Image، ولذلك لا تنشر الشركة CVE، وتوصي بأن تعمل دائماً على آخر Image.
- **الحجم**: كل شيء يعمل على سيرفر واحد، وتقول صفحة إخلاء المسؤولية إنه لن يتجاوز غالباً بضع مئات الآلاف من الأحداث دون جهد كبير في التوسع.
- **الميزات**: كل ميزات الخطط المدفوعة متاحة في السحابة فقط، ومنها تحليلات المجموعات Group Analytics ومسارات المستخدم المتقدمة وإدارة الصلاحيات RBAC وSAML.
- **الترخيص**: نسخة Docker Compose مجانية بترخيص MIT، و[ملف الترخيص](https://github.com/PostHog/posthog/blob/master/LICENSE?ref=arabroot.io) يستثني مجلد `ee/` الذي له ترخيص خاص.

وقد يتساءل البعض: رأينا في صفحة الإصدارات على GitHub إصدارات باسم [hobby-20261025-002803](https://github.com/PostHog/posthog/releases/tag/hobby-20261025-002803?ref=arabroot.io)، أليست هذه إصدارات للاستضافة الذاتية؟ والإجابة لا، فهذه الإصدارات فيها ملف واحد هو `hobby-installer`، وهو أداة تثبيت تفاعلية TUI ما زالت تجريبية Beta بحسب صفحة الاستضافة الذاتية، ووصف الإصدار يقول فقط إنها بنيت من Commit معين، وتاريخ الوسم هو تاريخ بناء أداة التثبيت وليس رقم إصدار لـ PostHog. لذلك فالذي تستطيع تثبيته فعلاً هو الـ Commit نفسه، وهذا ما سوف نفعله.

والجدول التالي يقارن الخيارين من حيث ما يهمك قبل القرار، والأرقام من [صفحة الأسعار](https://posthog.com/pricing?ref=arabroot.io) في 25 أكتوبر 2026:

| البند                   | PostHog السحابي Cloud                             | PostHog على سيرفرك Hobby           |
| ----------------------- | ------------------------------------------------- | ---------------------------------- |
| الأحداث مجاناً كل شهر   | أول مليون حدث، ثم 0.00005 دولار للحدث حتى مليونين | بلا حد من PostHog، والحد هو سيرفرك |
| تسجيل الجلسات           | أول 5 آلاف تسجيل، ثم 0.005 دولار للتسجيل          | بلا حد من PostHog                  |
| طلبات الـ Feature Flags | أول مليون طلب، ثم 0.0001 دولار للطلب              | بلا حد من PostHog                  |
| مكان البيانات           | منطقة أمريكية أو أوروبية                          | سيرفرك                             |
| الدعم والتحديث          | تديره الشركة                                      | عليك وحدك، وبلا دعم رسمي           |
| الميزات المدفوعة        | متاحة بالدفع                                      | غير متاحة                          |

لذلك إذا لم يكن عندك شرط يمنع خروج بيانات المستخدمين من سيرفرك، فالسحابة بخطتها المجانية أنسب لأغلب الشركات الناشئة كما شرحنا في [مقارنة أدوات النمو](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D9%85%D9%88-amplitude-%D9%88-mixpanel-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/)، أما إذا كان عندك هذا الشرط وعندك من يدير السيرفر فتابع معنا.

## التثبيت Installation بنسخة Hobby

الطريقة الرسمية هي سكربت `deploy-hobby`، وهو يسأل عن الوسم Tag والنطاق، والوسم الافتراضي هو `latest`. ولكن قبل أن تضغط Enter لاحظ أن `latest` يعني أن كل تثبيت جديد أو إعادة تشغيل بعد `pull` قد يأتي بكود مختلف، وهذا يتعارض مع قاعدة نلتزم بها في كل أدلة الموقع، وهي أن تعرف بالضبط ما الذي يعمل على سيرفرك. ولحسن الحظ أن السكربت يقبل رقم Commit كاملاً من 40 حرفاً بدلاً من الوسم، فيثبت الكود عليه ويستخدم Image بنفس الرقم، لذلك نمرر الـ Commit الذي اختبرناه ومعه النطاق:

```bash
mkdir -p /opt/posthog && cd /opt/posthog
curl -fsSL https://raw.githubusercontent.com/posthog/posthog/3dcce7bc74903c4593e2382b18937e3bafad00dd/bin/deploy-hobby -o deploy-hobby
bash deploy-hobby 3dcce7bc74903c4593e2382b18937e3bafad00dd analytics.example.com
```

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

- نزلنا السكربت من نفس الـ Commit وليس من `HEAD`، حتى لا يتغير السكربت نفسه بين يوم وآخر، ثم قرأناه قبل تشغيله، وهذا ما ننصحك به مع أي سكربت يطلب `sudo`.
- هذا الـ Commit هو الذي بنيت منه أداة التثبيت `hobby-20261025-002803`، وتاريخه 25 أكتوبر 2026، والـ Image الخاصة به `posthog/posthog:3dcce7bc74903c4593e2382b18937e3bafad00dd` موجودة على Docker Hub.
- السكربت يعمل على Ubuntu فقط، ويسأل في البداية عن مفاتيح Anthropic وOpenAI لميزات الذكاء الاصطناعي، واتركها فارغة إذا لم تحتجها.

وقد يتساءل البعض: ماذا يفعل هذا السكربت بالضبط؟ بعد أن قرأناه وجدنا أنه يقوم بما يلي:

1. يثبت git وDocker وDocker Compose إذا لم تكن موجودة، ثم ينسخ مستودع PostHog ويثبته على الـ Commit.
2. يكتب ملف `.env` فيه أسرار عشوائية مثل `POSTHOG_SECRET` و`ENCRYPTION_SALT_KEYS`، مع النطاق والوسم، ولا يكتب فوقه إذا كان موجوداً، فاحفظ هذا الملف مع النسخ الاحتياطي لأن ضياعه يفسد البيانات المشفرة.
3. ينزل قاعدة مواقع GeoLite2 إلى المجلد `share/` لتحويل عنوان IP إلى مدينة ودولة.
4. ينسخ `docker-compose.hobby.yml` إلى `docker-compose.yml`، ثم يشغل `docker-compose up -d --pull always`، وينتظر حتى يرجع `/_health` الرمز 200 لمدة أقصاها 10 دقائق.
5. يرسل حدثين إلى PostHog Cloud عند بدء التثبيت وانتهائه، فيهما النطاق الذي أدخلته، فإذا كان اسم النطاق نفسه سراً فاحذف هذين السطرين من السكربت قبل تشغيله.

والملف الذي يشغله السكربت فيه 39 خدمة، وعندما فحصنا الـ Images فيه وجدنا أن تثبيت الـ Commit لا يثبت كل شيء:

| الخدمات                                           | الـ Image                         | هل الإصدار ثابت؟          |
| ------------------------------------------------- | --------------------------------- | ------------------------- |
| web وworker وtemporal-django-worker               | posthog/posthog:3dcce7bc…         | نعم، بالـ Commit (9.2 GB) |
| plugins وخدمات ingestion وrecording-api           | posthog/posthog-node:latest       | لا، وسم latest (2.7 GB)   |
| capture وfeature-flags وcymbal وlivestream وغيرها | ghcr.io/posthog/posthog/\*:master | لا، وسم master            |
| Caddy                                             | caddy                             | لا، بدون وسم أصلاً        |
| ClickHouse وPostgreSQL وRedpanda وTemporal وغيرها | مثل postgres:15.12-alpine         | نعم                       |

لذلك بعد أول تشغيل ناجح سجل الـ Digest لكل Image، حتى تعرف ما الذي كان يعمل إذا تغير شيء بعد `pull` جديد:

```bash
cd /opt/posthog
for i in $(docker compose config --images | sort -u); do
  docker image inspect "$i" --format '{{index .RepoDigests 0}}'
done > images-$(date +%F).txt
```

### كم يستغرق الإقلاع الأول Startup؟

السكربت ينتظر 10 دقائق، والتوثيق يقول من 5 إلى 10 دقائق، ولكن في تجربتنا على 8 أنوية و31 GB من الذاكرة أخذ الإقلاع الأول 77 دقيقة حتى رجع `/_health` الرمز 200\. والسبب أن الـ Container المسمى web يطبق ترحيلات قاعدة البيانات Migrations قبل أن يبدأ الخادم، وطبق في تجربتنا 1215 ترحيلاً، وعملية `manage.py migrate` تعمل على نواة واحدة بنسبة 94% طوال هذا الوقت، فزيادة عدد الأنوية لا تسرعها. ويمكنك متابعة التقدم بالأمر التالي:

```bash
docker compose logs -f web | grep Applying
```

```text
web-1  |   Applying signals.0117_drop_signalscoutconfig_team_id_fk_idx... OK
web-1  |   Applying signals.0119_squash_2026_09_07_schema_addons... OK
```

لذلك إذا انتهت مهلة السكربت برسالة `Health check timed out after 10 minutes` فلا تحذف شيئاً ولا تعد التثبيت، وإنما تابع السجلات Logs حتى تنتهي الترحيلات، وخلال ذلك سوف يرجع الـ Reverse Proxy الرمز 502 لأن web لم يبدأ بعد. وإعادة التشغيل لاحقاً أسرع ولكنها ليست فورية، فبعد `docker compose stop` ثم `start` أخذ رجوع `/_health` إلى 200 حوالي 10 دقائق، منها 8 دقائق في التحقق من ترحيلات Django ثم ترحيلات جداول الأشخاص ثم ترحيلات ClickHouse، مع أن المخرج كان `No migrations to apply` و`Clickhouse migrations up to date!`، والسبب أن سكربت التشغيل يمر على كل الترحيلات في كل إقلاع.

وبعد الإقلاع افتح النطاق في المتصفح وأنشئ الحساب الأول والمنظمة، ثم من Settings انسخ Project token الذي يبدأ بـ `phc_`، وهو مفتاح كتابة عام يوضع في كود الصفحة، أي أنه ليس سراً، ولكنه لا يسمح بقراءة البيانات.

## الموارد: الذاكرة RAM والمعالج CPU والقرص Disk

بعد الإقلاع وإرسال أحداث التجربة قسنا الموارد بالأمر `docker stats`، فكان مجموع الذاكرة 18.5 GB لـ 37 Container تعمل، والمعالج حوالي 72% من نواة واحدة والنظام شبه خامل. والجدول التالي فيه أكبر المستهلكين:

| الخدمة                                  | الذاكرة                  | دورها                                                              |
| --------------------------------------- | ------------------------ | ------------------------------------------------------------------ |
| worker                                  | 7.1 GB                   | مهام Celery في الخلفية                                             |
| web                                     | 5.1 GB                   | الواجهة والـ API، بأربع عمليات Granian                             |
| temporal-django-worker                  | 1.4 GB                   | المهام الطويلة عبر Temporal                                        |
| ClickHouse                              | 621 MB                   | تخزين الأحداث والاستعلام عليها                                     |
| Kafka (Redpanda)                        | 621 MB                   | طابور الأحداث Queue                                                |
| Elasticsearch                           | 557 MB                   | يعمل رغم أن ملف hobby يوقف استخدام Temporal له بـ ENABLE\_ES=false |
| zookeeper                               | 415 MB                   | يحتاجه ClickHouse                                                  |
| خدمات Node.js الست (plugins وingestion) | بين 177 و278 MB لكل منها | معالجة الأحداث والتسجيلات                                          |

في الجدول أعلاه لاحظ أن worker وحده أخذ 7.1 GB، والسبب أن Celery يشغل افتراضياً عدداً من العمليات Processes يساوي عدد الأنوية، وكل عملية تحمل Django كاملاً بحوالي 1.2 إلى 1.5 GB، وفي تجربتنا كانت الأنوية 8\. لذلك على سيرفر من 4 vCPU يقل هذا الرقم، ويمكنك تحديد العدد بالمتغير `WEB_CONCURRENCY` الذي يقرأه سكربت `bin/docker-worker-celery`، ولكن لا تتوقع أن تعمل الحزمة كلها بأقل من 8 GB، وهذا ما يقوله السكربت نفسه في أول سطر يطبعه: «You REALLY need 8GB or more of memory».

أما القرص فبعد التجربة كان حجم الـ Volumes حوالي 2.4 GB، منها 1.9 GB لبيانات Redpanda التي تحجز مساحة مقاطعها مسبقاً، و172 MB لـ ClickHouse، و158 MB لـ PostgreSQL، وهذا قبل أي بيانات حقيقية، فأضفها إلى 28 GB للـ Images.

## إرسال الأحداث Events من تطبيقك

الآن سوف نقوم بربط التطبيق، وأبسط طريقة هي مقطع الكود Snippet الرسمي من [توثيق مكتبة JavaScript](https://posthog.com/docs/libraries/js?ref=arabroot.io)، وهو يحمل المكتبة من سيرفرك نفسه من المسار `/static/array.js`، فلا يتصل المتصفح بأي نطاق خارجي. وضعنا المقطع في ملف `ph.js` يحمل في كل صفحة، والجزء الذي يهمنا منه هو الإعداد:

```javascript
posthog.init('phc_xxxxxxxx', {
    api_host: 'https://analytics.example.com',
    defaults: '2026-08-30',
    person_profiles: 'identified_only',
    session_recording: { maskAllInputs: true },
});
```

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

- `api_host` هو نطاق سيرفرك، والمكتبة ترسل إليه الأحداث على `/e/` و`/i/v0/e/`، والتسجيلات على `/s/`، وطلبات الـ Flags على `/flags`، والـ Caddy المدمج يوجه كل مسار إلى خدمته كما في الصورة الأولى.
- `defaults` تاريخ يختار مجموعة الإعدادات الافتراضية، وثبته حتى لا يتغير سلوك المكتبة مع تحديثها.
- `identified_only` يعني أن PostHog لا ينشئ ملف شخص Person Profile للزائر المجهول إلا بعد `identify`، فتقل البيانات الشخصية المخزنة ويقل الحمل.
- `maskAllInputs` يخفي كل ما يكتب في حقول الإدخال من التسجيلات، وهو الافتراضي أصلاً كما في [صفحة خصوصية التسجيلات](https://posthog.com/docs/session-replay/privacy?ref=arabroot.io)، وكتبناه صراحة حتى لا يغيره أحد دون أن ينتبه.

والزيارات Pageviews والنقرات تلتقط تلقائياً، أما الأحداث التي تهمك في المنتج فترسلها بنفسك، مثل حدث إنشاء الفاتورة في صفحة `app.html`، وأضفنا معه حدثاً باسم عربي وخصائص عربية لنختبر العربية:

```javascript
posthog.capture('invoice_created', { amount_sar: amount, currency: 'SAR' });
posthog.capture('إرسال فاتورة', { 'طريقة الإرسال': 'واتساب', 'المدينة': 'الرياض' });
```

والصفحة التي اختبرنا عليها تبدو هكذا:

![صفحة لوحة التحكم في تطبيق فاتورتي باتجاه من اليمين إلى اليسار، وفيها نموذج فاتورة جديدة بحقل اسم العميل وفيه مؤسسة النخيل وحقل المبلغ وفيه 1150 وزر إنشاء الفاتورة، وتحته رسالة تم إنشاء الفاتورة وإرسالها](https://arabroot.io/content/images/2026/10/posthog-lab-03-demo-app.webp)

صفحة إنشاء الفاتورة في التطبيق التجريبي، وكل ضغطة على الزر ترسل حدثين

### هل وصلت الأحداث إلى ClickHouse؟

افتح Activity في الواجهة، أو استعلم مباشرة بلغة HogQL، وهي SQL على جداول PostHog، من صفحة SQL في الواجهة أو من الـ API. وبعد أن مررنا بالتطبيق من الصفحة الرئيسية إلى إنشاء الفاتورة شغلنا الاستعلام التالي:

```sql
select event, distinct_id, person.properties.email,
       properties.`طريقة الإرسال`, properties.`المدينة`
from events order by timestamp
```

والمخرج سوف يكون كما يلي، واختصرنا منه أحداث النقرات التلقائية:

```text
$pageview        01a173db-fc98-7851-af06-45fdbc36401f  sara@example.com
cta_clicked      01a173db-fc98-7851-af06-45fdbc36401f  sara@example.com
$pageview        01a173db-fc98-7851-af06-45fdbc36401f  sara@example.com
signed_up        user-sara                             sara@example.com
$identify        user-sara                             sara@example.com
$pageview        user-sara                             sara@example.com
invoice_created  user-sara                             sara@example.com
إرسال فاتورة     user-sara                             sara@example.com  واتساب  الرياض
```

في المخرج أعلاه لاحظ التالي:

- الأحداث الثلاثة الأولى أرسلت قبل التسجيل بمعرف عشوائي Anonymous ID، ومع ذلك يظهر بجانبها بريد سارة، لأن `identify` ربط المعرفين بنفس الشخص كما سنشرح في القسم التالي.
- الحدث العربي وصل باسمه، والخصائص العربية تقرأ في HogQL بين علامتي `` ` ``.

وفي صفحة Activity تظهر الأسماء العربية كما هي، حتى في وصف النقرات التلقائية الذي يأخذ نص الزر:

![صفحة Activity في PostHog بواجهة إنجليزية، وفيها جدول أحداث منها اشتراك مدفوع وإرسال فاتورة بالعربية، وclicked button with text إنشاء الفاتورة، وinvoice_created وsigned_up وPageview، وعمود الشخص فيه بريد المستخدم، وعمود الرابط فيه صفحات التطبيق التجريبي](https://arabroot.io/content/images/2026/10/posthog-lab-05-activity.webp)

الواجهة بالإنجليزية ومن اليسار إلى اليمين، والأسماء العربية تظهر في اتجاهها الصحيح داخل الجدول

### أحداث من السيرفر Server-side

بعض الأحداث لا يصح أن تأتي من المتصفح، مثل نجاح الدفع، لأن المتصفح قد يغلق قبل الإرسال، ولأن من يعرف الـ Token يستطيع أن يرسل ما يشاء. لذلك أرسلها من الكود الخاص بك بعد أن تتأكد من العملية، بنفس `distinct_id` الذي استخدمته في `identify`. والمثال التالي يرسل حدث اشتراك مدفوع بـ curl، والجسم في ملف لأن الاسم عربي:

```bash
cat > event.json <<'EOF'
{"api_key": "phc_xxxxxxxx", "event": "اشتراك مدفوع", "distinct_id": "user-sara",
 "properties": {"الخطة": "الاحترافية", "amount_sar": 49}}
EOF
curl -s -X POST https://analytics.example.com/i/v0/e/ \
  -H 'Content-Type: application/json' --data-binary @event.json
```

```text
{"status":"Ok"}
```

ولاحظ أن الرد `Ok` يعني أن خدمة الاستقبال وضعت الحدث في الطابور، وليس أنه وصل إلى ClickHouse، فإذا كان الـ Token خاطئاً أو تعطلت المعالجة فلن يخبرك الرد بذلك، والتحقق الحقيقي هو أن تراه في Activity أو في استعلام HogQL.

## من زائر مجهول إلى مستخدم معروف Identify

عندما يدخل الزائر لأول مرة تعطيه المكتبة معرفاً عشوائياً، وتحفظه في المتصفح، وكل أحداثه قبل التسجيل تحمل هذا المعرف. وعند التسجيل نستدعي `identify` بمعرف المستخدم من قاعدة بياناتك، فيربط PostHog المعرفين بشخص واحد، وتصبح أحداثه قبل التسجيل وبعده تاريخاً واحداً كما يقول [توثيق identify](https://posthog.com/docs/product-analytics/identify?ref=arabroot.io). وهذا الكود في صفحة التسجيل:

```javascript
var userId = 'user-' + email.split('@')[0];           // your own user id from the database
posthog.identify(userId, { email: email, store_name: store, plan: 'free' });
posthog.capture('signed_up', { method: 'email' });
```

![خط زمني من اليمين إلى اليسار: في البداية زائر مجهول بمعرف عشوائي وأحداثه pageview وcta_clicked، ثم دائرة identify عند التسجيل مع الكود posthog.identify، ثم مستخدم معروف بالمعرف user-sara وأحداثه signed_up وinvoice_created وإرسال فاتورة، والجهتان تتجهان إلى مربع شخص واحد Person في PostHog فيه email وplan وstore_name](https://arabroot.io/content/images/2026/10/posthog-lab-02-identity.webp)

الدالة identify تجعل الزائر قبل التسجيل والمستخدم بعده شخصاً واحداً

ولماذا يهمنا هذا؟ لأن القمع يحسب الأشخاص وليس المعرفات، فإذا نسيت `identify` سوف يظهر لك الزائر في الخطوة الأولى بمعرف، ثم يظهر في خطوة التسجيل بمعرف آخر كأنه شخص جديد، فيبدو القمع كأن أحداً لم يكمل. وفي المقابل لا تستخدم البريد نفسه معرفاً، لأنه قد يتغير، واستخدم رقم المستخدم الثابت في قاعدة بياناتك، وتذكر أن تستدعي `posthog.reset()` عند تسجيل الخروج حتى لا تختلط أحداث شخصين على نفس الجهاز.

## قمع التحويل Funnel: من الزيارة إلى أول فاتورة

ثلاث جلسات من المتصفح لا تكفي لرسم قمع له معنى، لذلك كتبنا سكربتاً صغيراً `seed.mjs` يولد بيانات تجريبية لـ 200 زائر على ستة أسابيع، يمر كل منهم بنفس الخطوات ويتوقف بعضهم في كل خطوة بنسب ثابتة، ويرسلها دفعة واحدة إلى `/batch/` بتواريخ قديمة. والأرقام في هذا القسم والذي بعده من هذه البيانات المولدة، وليست من مستخدمين حقيقيين:

```bash
PH_HOST=https://analytics.example.com PH_TOKEN=phc_xxxxxxxx node seed.mjs
```

```text
200 {"status":"Ok"} 477 events
{ '$pageview': 200, cta_clicked: 89, signed_up: 52, '$identify': 52, invoice_created: 84 }
```

بعد ذلك من Product analytics اختر New insight ثم Funnel، وأضف الخطوات بالترتيب: `$pageview` ثم `cta_clicked` ثم `signed_up` ثم `invoice_created`، واجعل نافذة التحويل Conversion Window أربعة عشر يوماً، والمدة آخر 45 يوماً:

![تقرير Funnel في PostHog بعنوان عربي قمع التسجيل من الزيارة إلى أول فاتورة، بأربعة أعمدة: Pageview بـ 203 أشخاص، وcta_clicked بـ 92 شخصاً بنسبة 45.32%، وsigned_up بـ 55 شخصاً بنسبة 27.09%، وinvoice_created بـ 37 شخصاً بنسبة 18.23%، ونسبة التحويل الكلية 18.23% ومتوسط الوقت للتحويل 10 دقائق](https://arabroot.io/content/images/2026/10/posthog-lab-06-funnel.webp)

القمع على البيانات المولدة: 18.23% من الزوار وصلوا إلى أول فاتورة

في التقرير أعلاه لاحظ التالي:

- من 203 زائرين ضغط 92 على زر «ابدأ مجاناً»، وسجل 55، وأنشأ 37 فاتورة، أي أن أكبر خسارة في الخطوة الأولى، حيث غادر 111 زائراً دون أن يضغطوا الزر، وهنا تبدأ بتحسين الصفحة الرئيسية قبل نموذج التسجيل.
- الرقم 203 وليس 200 لأن الجلسات الثلاث التي مررنا بها من المتصفح دخلت نفس القمع، وهذا يذكرك بأن تستبعد حسابات الفريق والاختبار من التقارير.
- تحت كل خطوة الوقت الذي استغرقه الانتقال إليها، ومنه تعرف أي خطوة تأخذ وقتاً أطول من المتوقع.

وتعريف التفعيل Activation، أي متى نعتبر المستخدم وصل إلى قيمة المنتج، شرحناه في [مقال التفعيل والاحتفاظ](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D9%81%D8%B9%D9%8A%D9%84-activation-%D9%88%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%81%D8%A7%D8%B8-retention-%D9%84%D9%85%D8%A7%D8%B0%D8%A7-%D9%8A%D8%BA%D8%A7%D8%AF%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%88%D9%86/)، وهنا اخترنا أول فاتورة كمثال.

## الاحتفاظ Retention: هل يعود من سجل؟

القمع يخبرك كم شخصاً وصل، والاحتفاظ يخبرك كم شخصاً بقي. من New insight اختر Retention، واجعل الحدث الأول `signed_up`، والحدث الذي يعني العودة `invoice_created`، والفترة أسبوعية:

![تقرير Retention في PostHog بعنوان عربي الاحتفاظ الأسبوعي من سجل ثم أنشأ فاتورة، فيه رسم خطوط لكل مجموعة وجدول مجموعات Cohorts أسبوعية من Sep 13 إلى Oct 24 بأحجام بين 7 و13، ومتوسط العودة في الأسبوع الأول 41.9%](https://arabroot.io/content/images/2026/10/posthog-lab-07-retention.webp)

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

وكل صف في الجدول مجموعة Cohort من المستخدمين سجلوا في نفس الأسبوع، فمثلاً مجموعة 20 إلى 26 سبتمبر فيها 8 مستخدمين، وعاد 75% منهم لإنشاء فاتورة في الأسبوع الأول، أما الخلايا الفارغة في آخر كل صف فهي أسابيع لم تأت بعد. والذي تبحث عنه في الحياة الحقيقية هو هل يستقر المنحنى عند نسبة معينة أم ينزل إلى الصفر، لأن المنحنى الذي يستقر يعني أن هناك مجموعة وجدت قيمة في المنتج.

## تسجيل الجلسات Session Replay

التسجيل متوقف افتراضياً في المشروع الجديد، وتفعله من Settings ثم Session replay ثم Record user sessions، وبعدها تحمل المكتبة ملف `posthog-recorder.js` من سيرفرك وتبدأ إرسال التسجيل إلى `/s/`. والتسجيل ليس فيديو، وإنما تغييرات الصفحة DOM مسجلة بمكتبة rrweb، لذلك يعاد بناء الصفحة في المتصفح عند المشاهدة:

![مشغل التسجيلات في PostHog يعرض صفحة فاتورة جديدة من التطبيق التجريبي باتجاه من اليمين إلى اليسار، وحقل اسم العميل وحقل المبلغ مكتوب فيهما نجوم بدلاً من القيم، ومؤشر الماوس على زر إنشاء الفاتورة، وتحته رسالة تم إنشاء الفاتورة وإرسالها](https://arabroot.io/content/images/2026/10/posthog-lab-08-replay.webp)

إعادة تشغيل جلسة: الاتجاه العربي محفوظ، وقيم الحقول مخفية بالنجوم

قارن هذه الصورة بصورة الصفحة الأصلية في قسم [إرسال الأحداث](#capture)، فالصفحة أعيدت باتجاهها من اليمين إلى اليسار وبنصوصها العربية، أما «مؤسسة النخيل» و«1150» فظهرت نجوماً، لأن الإخفاء يتم في المتصفح قبل الإرسال، أي أن القيمة لا تصل إلى سيرفرك أصلاً. ولكن النصوص الثابتة في الصفحة تسجل كما هي، فإذا كانت صفحتك تعرض بيانات عميل خارج حقول الإدخال، مثل اسمه في رأس الصفحة، فأضف لها الكلاس `ph-no-capture` كما في صفحة الخصوصية.

## الإطلاق التدريجي Feature Flags

مفهوم الـ Feature Flags والإطلاق التدريجي شرحناه في [مقال Feature Flags](https://arabroot.io/articles/%D9%85%D9%81%D9%87%D9%88%D9%85-feature-flags-%D9%88%D8%A7%D9%84%D8%A7%D8%B7%D9%84%D8%A7%D9%82-%D8%A7%D9%84%D8%AA%D8%AF%D8%B1%D9%8A%D8%AC%D9%8A-gradual-rollout/)، وهنا نطبقه: نريد أن نعرض شريط «خصم 20% على الخطة السنوية» في صفحة الأسعار لنصف المستخدمين فقط. من Feature flags أنشأنا Flag بالمفتاح `annual-discount-banner` وشرط واحد لـ 50% من المستخدمين:

![صفحة Feature Flag في PostHog بالمفتاح annual-discount-banner ووصف عربي شريط خصم الخطة السنوية في صفحة الأسعار، والـ Flag مفعل، ونوعه Boolean، وشرط الإطلاق Set 1 يطابق 50% من المستخدمين](https://arabroot.io/content/images/2026/10/posthog-lab-09-flag.webp)

Flag من نوع Boolean مفعل لنصف المستخدمين

وفي صفحة الأسعار نقرأ الـ Flag بعد أن تحمل المكتبة قيم الـ Flags:

```javascript
posthog.onFeatureFlags(function () {
  var on = posthog.isFeatureEnabled('annual-discount-banner');
  document.getElementById('banner').style.display = on ? 'block' : 'none';
});
```

ولكي نرى كيف يقسم PostHog المستخدمين، سألنا الـ API عن ستة مستخدمين بـ curl:

```bash
for u in user-sara user-omar user-lina user-khalid user-noura user-fahad; do
  printf "%s " $u
  curl -s https://analytics.example.com/flags/?v=2 -H 'Content-Type: application/json' \
    -d "{\"api_key\":\"phc_xxxxxxxx\",\"distinct_id\":\"$u\"}" \
  | jq -r '.flags."annual-discount-banner" | "\(.enabled) \(.reason.description)"'
done
```

```text
user-sara false Out of rollout bound
user-omar true Matched condition set 1
user-lina false Out of rollout bound
user-khalid false Out of rollout bound
user-noura true Matched condition set 1
user-fahad false Out of rollout bound
```

في المخرج أعلاه لاحظ أن اثنين من ستة حصلا على الشريط وليس ثلاثة، والسبب أن الـ 50% نسبة على عدد كبير من المستخدمين وليست قسمة دقيقة لكل مجموعة صغيرة، فالقرار يحسب من هاش Hash لمعرف المستخدم ومفتاح الـ Flag. وهذا يعني أيضاً أن المستخدم نفسه يحصل على نفس النتيجة في كل زيارة ومن كل جهاز بعد `identify`، فعمر يرى الشريط دائماً وسارة لا تراه:

![صفحة الأسعار في تطبيق فاتورتي كما يراها المستخدم عمر، وفي أعلاها شريط أخضر مكتوب فيه خصم 20% على الخطة السنوية، وتحته بطاقة الخطة المجانية 20 فاتورة شهرياً وبطاقة الخطة الاحترافية 49 ريالاً شهرياً](https://arabroot.io/content/images/2026/10/posthog-lab-04-flag-on.webp)

صفحة الأسعار لمستخدم داخل الـ 50%: الشريط ظاهر

وكل قراءة للـ Flag من المتصفح تسجل حدث `$feature_flag_called`، فتستطيع أن تقسم القمع بحسب قيمة الـ Flag وترى هل الشريط غير شيئاً. ولكن قياس أثر التغيير بدقة إحصائية موضوع المقال التالي [اختبارات A/B مع GrowthBook](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-ab-%D8%A8%D8%AF%D9%82%D8%A9-%D9%85%D8%B9-growthbook-%D8%B9%D9%84%D9%89-%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D9%83/)، لأن التجارب Experiments في PostHog تعمل فوق نفس الـ Flags.

## العربية واتجاه الواجهة RTL

جمعنا هنا ما وجدناه عن العربية في هذه التجربة:

| ماذا اختبرنا                    | النتيجة                                                      |
| ------------------------------- | ------------------------------------------------------------ |
| اسم حدث عربي مثل «إرسال فاتورة» | يحفظ ويظهر كما هو في Activity والتقارير                      |
| مفاتيح وقيم خصائص عربية         | تحفظ، وتقرأ في HogQL بين علامتي \`                           |
| عنوان تقرير ووصف Flag بالعربية  | يظهر في اتجاهه الصحيح داخل الواجهة                           |
| تسجيل صفحة عربية RTL            | يعاد بناؤها باتجاهها ونصوصها                                 |
| لغة الواجهة نفسها               | إنجليزية فقط ومن اليسار إلى اليمين، ولم نجد إعداداً لتغييرها |

ومع ذلك ننصحك بأن تكون أسماء الأحداث بالإنجليزية مثل `invoice_created`، وأن تكون القيم المعروضة للمستخدم بالعربية، والسبب أن الأسماء الإنجليزية أسهل في الكود وفي استعلامات SQL وفي البحث في الواجهة، ولا تحتاج علامات اقتباس خاصة. ولاحظ أيضاً أن العربية قد تفسد قبل أن تصل إلى PostHog إذا أرسلتها من طرفية لا تستخدم UTF-8، فقد أرسلنا حدثاً بخاصية عربية من طرفية Windows مباشرة في سطر الأمر فوصلت علامات استفهام `???????`، وعندما وضعنا نفس الجسم في ملف وأرسلناه بـ `--data-binary` وصلت سليمة.

## الخصوصية Privacy والبيانات الشخصية

تشغيل PostHog على سيرفرك يحل سؤال «أين البيانات»، ولكنه لا يحل سؤال «ماذا تجمع»، وتحليلات المنتج أخطر في ذلك من تحليلات الموقع، لأنك هنا تربط الأحداث ببريد شخص معروف وتسجل جلساته. وقد شرحنا موقف نظام حماية البيانات الشخصية من التتبع في [مقارنة أدوات تحليلات الموقع](https://arabroot.io/articles/%D8%AA%D8%AD%D9%84%D9%8A%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%AF%D9%88%D9%86-google-analytics-%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%B9%D9%85%D9%84%D9%8A%D8%A9/) ومن رسائل التسويق في [قياس التسويق](https://arabroot.io/articles/%D9%82%D9%8A%D8%A7%D8%B3-%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D9%82-%D8%B1%D9%88%D8%A7%D8%A8%D8%B7-utm-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D9%86%D8%A7%D8%AF-attribution/)، وما يخص PostHog تحديداً:

- المكتبة تحفظ معرف الزائر في `localStorage` وفي Cookie، وهذا ما رأيناه في الإعداد الذي طبعه وضع التصحيح `posthog.debug()`: `"persistence":"localStorage+cookie"`، فاذكر ذلك في سياسة الخصوصية.
- لا ترسل في `identify` إلا ما تحتاجه في التحليل، فالخطة ونوع المتجر يكفيان غالباً، والبريد يمكن الوصول إليه من قاعدة بياناتك بالمعرف.
- أبق إخفاء الحقول في التسجيلات، وأضف `ph-no-capture` للعناصر التي تعرض بيانات حساسة.
- قاعدة GeoLite2 تحول عنوان IP إلى مدينة، وعنوان IP نفسه يحفظ مع الحدث في الخاصية `$ip`، وفي إعدادات المشروع خيار لتجاهله إذا لم تحتجه.

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

- `curl -s -o /dev/null -w '%{http_code}' https://analytics.example.com/_health` يرجع 200.
- `docker compose ps` يظهر كل الخدمات Up، ما عدا `kafka-init` الذي ينتهي بـ `Exited (0)` بعد إنشاء الـ Topics.
- بعد زيارة التطبيق يظهر `$pageview` في Activity خلال ثوان، وبعد التسجيل تظهر الأحداث السابقة بنفس بريد المستخدم.
- القمع والاحتفاظ يعرضان أرقاماً، والتسجيل يفتح بصفحتك وحقولها مخفية، والـ Flag يرجع `true` لبعض المستخدمين و`false` لغيرهم.

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

البيانات موزعة على عدة Volumes، أهمها `postgres-data` ففيه الحسابات والتقارير والـ Flags والأشخاص، و`clickhouse-data` ففيه الأحداث، و`seaweedfs` و`objectstorage-data` ففيهما التسجيلات. وPostgreSQL تستطيع نسخها والنظام يعمل:

```bash
cd /opt/posthog
docker compose exec -T db pg_dump -U posthog -Fc posthog > /backup/posthog-pg-$(date +%F).dump
```

وقد أخذ هذا الأمر 4 ثوان وأنتج ملفاً بحجم 3.6 MB في تجربتنا. أما ClickHouse فأمر `BACKUP` فيه لا يعمل في هذه النسخة، لأنه يحتاج إعداد `backups.allowed_path` غير الموجود في ملفات الإعداد، والخطأ الذي يظهر هو `The 'backups.allowed_path' configuration parameter is not set`. لذلك الطريقة الأبسط أن توقف النظام وتنسخ الـ Volumes كلها مع ملف `.env`:

```bash
cd /opt/posthog
docker compose stop
for v in postgres-data clickhouse-data seaweedfs objectstorage-data; do
  docker run --rm -v posthog_$v:/data -v /backup:/backup alpine:3.20 \
    tar czf /backup/posthog-$v-$(date +%F).tgz -C /data .
done
cp .env /backup/posthog-env-$(date +%F)
docker compose start
```

وفي تجربتنا أخذ نسخ الـ Volumes الأربعة 33 ثانية بعد الإيقاف، وكان حجم ملف ClickHouse المضغوط 73 MB وملف PostgreSQL 24 MB. وفي الأوامر أعلاه لاحظ أن اسم الـ Volume يبدأ باسم المجلد `posthog_` لأن Docker Compose يأخذ اسم المشروع من اسم المجلد، فتأكد منه بالأمر `docker volume ls`. ولاحظ أيضاً أن التشغيل بعد الإيقاف يأخذ حوالي 10 دقائق كما رأينا، فاختر وقتاً هادئاً، والأحداث التي ترسلها المتصفحات خلال التوقف تضيع، لأن خدمة الاستقبال نفسها متوقفة. وللاستعادة أنشئ الـ Volumes بنفس الأسماء، وفك الملفات فيها بنفس الطريقة، وضع ملف `.env` نفسه، ثم شغل `docker compose up -d`.

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

الطريقة الرسمية هي سكربت `upgrade-hobby` من المستودع، ولكنه مكتوب لمن ثبت `latest`، حيث يدخل مجلد الكود وينفذ `git pull --prune`، وهذا الأمر لا يعمل إذا كان الكود مثبتاً على Commit كما فعلنا، والخطأ الذي سوف تراه هو:

```text
You are not currently on a branch.
Please specify which branch you want to merge with.
```

والسكربت يعمل بـ `set -e` فيتوقف عند هذا السطر قبل أن يغير الـ Containers. لذلك نحدث بأنفسنا بنفس الخطوات التي يقوم بها السكربت، بعد أن نأخذ نسخة احتياطية كما تطلب صفحة الاستضافة الذاتية:

```bash
cd /opt/posthog
NEW=NEW_COMMIT_SHA
git -C posthog fetch origin
git -C posthog checkout "$NEW"
sed -i "s/^POSTHOG_APP_TAG=.*/POSTHOG_APP_TAG=$NEW/" .env
cp posthog/docker-compose.base.yml docker-compose.base.yml
cp posthog/docker-compose.hobby.yml docker-compose.yml
cp posthog/.env.services .env.services
docker compose pull
docker compose up -d
```

وقبل ذلك اقرأ `bin/upgrade-hobby` في الـ Commit الجديد، لأنه يحمل أحياناً خطوات خاصة ببعض التحديثات، مثل نقل الملفات من MinIO إلى SeaweedFS أو ترقية PostgreSQL من 12 إلى 15، فإذا وجدت خطوة منها تنطبق عليك فنفذها قبل `up -d`.

ولأن PostHog لا تصدر إصدارات مرقمة ولا ملاحظات إصدار Release Notes للاستضافة الذاتية، فالمرجع الوحيد لما تغير هو [صفحة التغييرات](https://posthog.com/changelog?ref=arabroot.io) وسجل الـ Commits، لذلك لا تحدث مباشرة على سيرفر الإنتاج، وإنما جرب الـ Commit الجديد أولاً على نسخة من البيانات، وراقب وقت الترحيلات، فإذا كان التحديث يحمل مئات الترحيلات فقد يتوقف الاستقبال وقتاً طويلاً كما رأينا في الإقلاع الأول. وتذكر أن خدمات `posthog-node:latest` و`*:master` سوف تتغير مع كل `pull` حتى لو لم تغير الـ Commit، وهذا سبب آخر لتسجيل الـ Digests قبل التحديث وبعده.

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

### الأحداث لا تصل من متصفح الاختبارات Headless

إذا كنت تختبر صفحاتك بمتصفح Headless مثل Playwright أو Puppeteer فسوف تجد أن الطلب الوحيد الذي يخرج هو `/flags`، ولا يخرج أي طلب إلى `/e/`. وعندما شغلنا `posthog.debug()` ظهرت الرسالة التالية:

```text
[PostHog.js] Event "dbg_event" was not captured because the user agent looks like a bot. Set `opt_out_useragent_filter: true` to capture it.
```

والسبب أن الـ User Agent فيه كلمة `HeadlessChrome`، فتعتبره المكتبة روبوتاً وتتجاهل أحداثه، وهذا سلوك جيد في الإنتاج، ولكنه يربك من يختبر. لذلك في الاختبارات ضع User Agent عادياً، أو فعل `opt_out_useragent_filter` في بيئة الاختبار فقط.

### الصفحة ترجع 502 بعد التثبيت

هذا يعني أن Caddy يعمل ولكن web لم يبدأ بعد، وغالباً لأن الترحيلات ما زالت تعمل، فتأكد بالأمر `docker compose logs --tail 5 web`، فإذا رأيت أسطر `Applying` فانتظر. وإذا رأيت خطأ بدلاً منها فهذا هو المكان الذي تبدأ منه، كما تقول صفحة الاستضافة الذاتية.

### شريط أحمر «running in PRODUCTION mode without TLS»

يظهر هذا الشريط أسفل الواجهة إذا فتحتها بـ `http://`، وظهر لنا في التجربة لأننا شغلناها على جهاز محلي بدون نطاق، أما على السيرفر مع النطاق فإن Caddy يطلب الشهادة ويختفي الشريط، فإذا بقي فتأكد من سجل `A` ومن أن المنفذين 80 و443 مفتوحان.

### لا توجد تسجيلات Recordings رغم أن الأحداث تصل

تأكد أولاً من تفعيل Record user sessions في الإعدادات، ثم من المتصفح استدع `posthog.sessionRecordingStarted()`، فإذا رجع `true` فالتسجيل يرسل، وابحث عنه برقم الجلسة الذي ترجعه `posthog.get_session_id()` في الرابط `/replay/<session_id>`. والتسجيلات تمر بخدمة `ingestion-sessionreplay`، فإذا لم تظهر فابحث في سجلاتها عن `session_batch_recorder_flushing`، لأنه السطر الذي يعني أنها حفظت دفعة من التسجيلات.

## إزالة التجربة

إذا كنت جربت على جهازك وتريد إزالة كل شيء، ومنه البيانات، فاستخدم الأمر التالي، وانتبه إلى أن `-v` يحذف الـ Volumes:

```bash
cd /opt/posthog
docker compose down -v --rmi all
```

والخيار `--rmi all` يحذف الـ Images التي يستخدمها الملف، وهي حوالي 28 GB، فإذا كان على السيرفر نظام آخر يستخدم نفس Images مثل `postgres:15.12-alpine` فاحذف Images PostHog وحدها بدلاً منه.

## الخلاصة

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

- PostHog تقول صراحة إن الاستضافة الذاتية غير مدعومة، وبلا إصدارات مرقمة ولا CVE، وإن الميزات المدفوعة في السحابة فقط، وإصدارات `hobby-*` على GitHub هي أداة التثبيت وليست إصدارات للمنتج.
- تستطيع تثبيت الكود بتمرير رقم الـ Commit إلى `deploy-hobby`، ولكن خدمات Node.js وRust وCaddy تبقى على `latest` و`master`، فسجل الـ Digests.
- الحزمة 39 خدمة و28 GB من الـ Images، واستهلكت 18.5 GB من الذاكرة، وأخذ الإقلاع الأول 77 دقيقة بسبب 1215 ترحيلاً، وإعادة التشغيل حوالي 10 دقائق.
- `identify` يجعل الزائر قبل التسجيل والمستخدم بعده شخصاً واحداً، وبدونه يفقد القمع معناه.
- القمع والاحتفاظ وتسجيل الجلسات والـ Feature Flags عملت كلها، والتسجيل يخفي الحقول قبل الإرسال ويحفظ اتجاه الصفحة العربية.
- الأحداث والخصائص العربية تعمل، والواجهة نفسها إنجليزية فقط، والأفضل أن تبقى أسماء الأحداث بالإنجليزية.

وأقرب المقالات إلى هذا الموضوع على الموقع:

[![](https://arabroot.io/content/images/2026/10/web-analytics-tools-cover-cover.webp)دليلتحليلات الموقع دون Google Analytics: مقارنة عمليةشغلنا Umami وPlausible وMatomo على نفس الموقع وقارناها بـ Google Analytics في حجم السكربت والـ Cookies والعربية وملكية البيانات.](https://arabroot.io/articles/%D8%AA%D8%AD%D9%84%D9%8A%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%AF%D9%88%D9%86-google-analytics-%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%B9%D9%85%D9%84%D9%8A%D8%A9/)[![](https://arabroot.io/content/images/2026/10/marketing-analytics-cover-cover.webp)دليلقياس التسويق: روابط UTM والإسناد Attributionكيف تعرف القناة التي جاءت بكل عميل: روابط UTM بقواعد تسمية ثابتة، وتحويل تعرفه أنت، ونموذج إسناد تفهم ما يحسبه.](https://arabroot.io/articles/%D9%82%D9%8A%D8%A7%D8%B3-%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D9%82-%D8%B1%D9%88%D8%A7%D8%A8%D8%B7-utm-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D9%86%D8%A7%D8%AF-attribution/)[![](https://arabroot.io/content/images/2026/10/ab-testing-cover-cover.webp)دليلاختبارات A/B بدقة مع GrowthBook على سيرفركنشغل GrowthBook مع PostgreSQL ونجري تجربة نعرف فرقها الحقيقي مسبقاً، لنتعلم قراءة فرصة الفوز والهامش وقيمة p دون أن نخدع أنفسنا.](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-ab-%D8%A8%D8%AF%D9%82%D8%A9-%D9%85%D8%B9-growthbook-%D8%B9%D9%84%D9%89-%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D9%83/)[![](https://arabroot.io/content/images/2026/10/growth-tools-cover-cover.webp)دليلأدوات النمو: Amplitude وMixpanel وبدائلها مفتوحة المصدرمقارنة أدوات تحليلات المنتج والتجارب والإحالة بالترخيص وآخر إصدار والسعر، مع اختبار فعلي للعربية في GrowthBook وUnleash.](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D9%85%D9%88-amplitude-%D9%88-mixpanel-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/)[![](https://arabroot.io/content/images/2026/10/activation-retention-cover-cover.webp)دليلالتفعيل Activation والاحتفاظ Retention: لماذا يغادر المستخدمون؟كيف تجد اللحظة التي يرى فيها المستخدم قيمة منتجك، وتقرأ منحنى الاحتفاظ والتسرب، مع تجربة تحسبها بـ Python وSQL.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D9%81%D8%B9%D9%8A%D9%84-activation-%D9%88%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%81%D8%A7%D8%B8-retention-%D9%84%D9%85%D8%A7%D8%B0%D8%A7-%D9%8A%D8%BA%D8%A7%D8%AF%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%88%D9%86/)[![](https://arabroot.io/content/images/2026/10/product-metrics-cover-cover-1.webp)دليلمقاييس المنتج Product Metrics وكيف تعرف أن الميزة نجحتكيف تختار مقياساً رئيسياً North Star لمنتجك، وتبني قمع التحويل Funnel وجدول العودة Retention من أحداث تطبيقك بأدوات تستضيفها على سيرفرك.](https://arabroot.io/articles/%D9%85%D9%82%D8%A7%D9%8A%D9%8A%D8%B3-%D8%A7%D9%84%D9%85%D9%86%D8%AA%D8%AC-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-product-metrics/)

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

- أكتوبر 2026: كتابة الدليل واختباره على PostHog Hobby من الـ Commit 3dcce7bc (أداة التثبيت hobby-20261025-002803) مع clickhouse-posthog 26.9.5.2 وpostgres:15.12-alpine وredpanda v25.1.9 وposthog-js 1.438.21 على Docker Engine 29.8.0، ببيانات تجريبية مولدة لـ 200 زائر.