استضافة Zitadel ذاتياً على VPS باستخدام Docker
يحتاج Zitadel إلى 4 أنوية و8 GB من RAM. أعدد PostgreSQL وmasterkey وTLS وSMTP والنسخ الاحتياطية على VPS واحد، وافهم أثر الترقية على قاعدة البيانات.
ما تحتاج إليه لاستضافة Zitadel ذاتياً على VPS
لاستضافة Zitadel ذاتياً على VPS، تحتاج إلى مضيف Docker، واسم DNS عام يشير إليه، وPostgreSQL، ونحو 4 أنوية CPU مع 8 GB من RAM. Zitadel هو موفّر هوية. ويصدر الرموز عبر OIDC (OpenID Connect) وSAML (security assertion markup language)، حتى تتوقف خدماتك الأخرى عن الاحتفاظ بقوائم المستخدمين الخاصة بها. التثبيت عبارة عن curl وdocker compose up. أما العناصر التي تحدد بقاء الخدمة فتشمل masterkey، ومستخدم قاعدة البيانات، وSMTP (simple mail transfer protocol)، والنسخة الاحتياطية، وأول ترقية.
يفترض كل ما يلي استخدام Ubuntu 24.04، وDocker Engine بالإصدار 24 أو أحدث مع Compose plugin، واسم مثل auth.example.com يُحلّ مسبقاً إلى عنوان الخادم.
ما مقدار موارد VPS التي يحتاجها Zitadel؟
تطلب بداية التشغيل السريعة باستخدام Compose في وثائق Zitadel ذاكرة RAM بسعة 2 GB. هذا الرقم مناسب لحاسوب محمول. ينشر دليل الإنتاج الخاص بـZitadel أرقاماً مختلفة.
The data behind this chart
[
{
"config": "Process floor, no load",
"cpu_cores": 0.5,
"ram_gb": 0.5
},
{
"config": "Single node, reduced setup",
"cpu_cores": 4,
"ram_gb": 8
},
{
"config": "HA node, logs and metrics on",
"cpu_cores": 4,
"ram_gb": 16
}
]هذه توصيات منشورة وليست قياسات من خادم قيد التشغيل. اقرأها بوصفها تصوراً لحجم المشكلة. تستهلك عملية Zitadel نفسها قدراً صغيراً من الموارد، نحو 0.5 GB من RAM في حالة الخمول. وتُخصَّص الأنوية لتجزئة كلمات المرور، وهي عملية بطيئة عمداً، لذلك تؤدي دفعة من عمليات تسجيل الدخول إلى ارتفاع مفاجئ في استخدام CPU. أما PostgreSQL فهو الجزء الآخر من المتطلبات: يقدّر الدليل نفسه نحو نواة واحدة لكل 100 طلب في الثانية، و4 GB من RAM لكل نواة. عند جمع هذين الجزأين، تصل إلى 4 أنوية و8 GB، وهي الموارد التي يذكرها الدليل لعقدة واحدة، أو 16 GB لكل عقدة بعد تفعيل السجلات والمقاييس.
لذلك سيبدأ هذا المكدس على VPS بسعة 2 GB، لكنه أقل من الموارد التي يوصي بها المشروع لأي استخدام فعلي. تسجيل الدخول هو الخدمة التي تعتمد عليها كل خدمة أخرى. عندما تتوقف، لا تسمح أي خدمة تثق بها لأي مستخدم بالدخول. من المعقول أن تقرر أن 8 GB أكثر مما تريد إنفاقه على المصادقة، ومن الأرخص بكثير اتخاذ هذا القرار الآن بدلاً من اتخاذه بعد الترحيل. يغطي مقارنة Keycloak وAuthentik وZitadel تكلفة كل خيار من حيث الذاكرة والجهد التشغيلي، ويكون خادم Authentik مستضافاً ذاتياً هو الخيار المعتاد على خادم أصغر.
الحصول على المكدس وتثبيت إصدار
mkdir zitadel-compose && cd zitadel-compose
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/.env.example
cp .env.example .env
chmod 600 .envيعرّف هذا الملف أربع خدمات ستشغّلها فعلياً. Traefik هو الـreverse proxy؛ إذ يوجّه الطلبات حسب المسار، وينهي TLS (أمان طبقة النقل) باستخدام الـoverlay الوارد لاحقاً. zitadel-api هو الملف التنفيذي المكتوب بلغة Go ويستمع على المنفذ 8080. zitadel-login هي واجهة تسجيل الدخول المقدَّمة على /ui/v2/login. postgres يحتوي على كل شيء. توجد ذاكرة Redis مؤقتة ومجمّع OpenTelemetry في الملف نفسه خلف ملفات Compose التعريفية، وتبقيان متوقفتين إلى أن تطلب تشغيلهما.
لا تشغّل docker compose up بعد. ينشئ التشغيل الأول المثيل، ولا يمكن تغيير عدة إعدادات أدناه بعد ذلك من دون عمل إضافي.
يُثبّت .env الذي نسخته علامات صورته الخاصة:
ZITADEL_VERSION=v4.16.0
TRAEFIK_IMAGE=traefik:v3.7.7
POSTGRES_IMAGE=postgres:17.10-alpineالإصدار الحالي من السلسلة v4 هو v4.17.1، وقد نُشر في 14 August 2026. اضبط ZITADEL_VERSION على الإصدار الذي تنوي تشغيله، والتزم بالسلسلة v4 بدلاً من تتبّع أحدث إصدار متاح. يسحب curl الوارد أعلاه docker-compose.yml من فرع main، وهذا الفرع غير مثبّت على أي إصدار؛ لذلك ثبّت نسختك من الملفين في مستودع git. وإلا فسيعطي الأمر نفسه على خادم جديد الشهر المقبل ملفاً مختلفاً، ولن تعرف ما الذي تغيّر.
امنح Postgres مستخدماً خاصاً وكلمة مرور فعلية
يتصل .env المشحون بـPostgreSQL باستخدام حساب superuser، مع كلمة المرور postgres:
POSTGRES_ADMIN_USER=postgres
POSTGRES_ADMIN_PASSWORD=postgres
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://postgres:postgres@postgres:5432/zitadel?sslmode=disableتوجد هنا نقطة مهمة في خطوة التحصين. تطلب منك وثائق Zitadel إلحاق POSTGRES_ZITADEL_PASSWORD بـ.env، لكن docker-compose.yml الأساسي لا يقرأ هذا المتغير مطلقاً، لذلك لا يغيّر ضبطه شيئاً. أما تغيير POSTGRES_ADMIN_PASSWORD وحده فيؤدي إلى قطع الاتصال، لأن كلمة المرور مكتوبة أيضاً حرفياً داخل سلسلة DSN (اسم مصدر البيانات). سلسلة DSN هي السطر الذي يحدد طريقة اتصال Zitadel.
توضح التعليقات في .env.example بقية الأمر صراحةً: عند ضبط DSN، يستخدم Zitadel ذلك المستخدم مباشرةً ولا ينشئ لك مستخدماً غير مميّز، لذلك يجب أن يكون الدور موجوداً قبل التشغيل الأول. أنشئ كلمة مرور، وشغّل Postgres منفرداً، وأنشئ الدور.
tr -dc A-Za-z0-9 </dev/urandom | head -c 32; echo
docker compose --env-file .env -f docker-compose.yml up -d postgres
docker compose --env-file .env -f docker-compose.yml exec -T postgres \
psql -U postgres -d postgres <<'SQL'
CREATE ROLE zitadel LOGIN PASSWORD 'the-password-you-generated';
ALTER DATABASE zitadel OWNER TO zitadel;
SQL
docker compose --env-file .env -f docker-compose.yml exec -T postgres \
psql -U postgres -d zitadel -c 'ALTER SCHEMA public OWNER TO zitadel;'تُنفَّذ استدعاءات psql داخل الحاوية عبر socket المحلي، الذي تثق به صورة Postgres الرسمية، لذلك لا تطلب كلمة مرور. الملكية هي الجزء المهم. في PostgreSQL 15 والإصدارات الأحدث، لا يتيح GRANT ALL PRIVILEGES ON DATABASE العادي للدور إنشاء جداول في مخطط public، لذلك تفشل مرحلة إعداد Zitadel بخطأ صلاحيات أثناء إنشاء مخططاته. تجنّب ذلك بجعل الدور مالكاً لقاعدة البيانات والمخطط.
وجّه DSN الآن إلى الدور الجديد، واضبط كلمة مرور admin فعلية أثناء وجودك في الملف:
POSTGRES_ADMIN_PASSWORD=a-32-character-random-string
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://zitadel:the-password-you-generated@postgres:5432/zitadel?sslmode=disableيُعد sslmode=disable مناسباً هنا لأن Postgres لا يمكن الوصول إليه إلا عبر شبكة Compose الخاصة، ولا يُنشر منفذه إلى المضيف مطلقاً. بعد التشغيل الكامل الأول، تحقّق من أن الدور يملك بياناته فعلاً:
docker compose exec -T postgres psql -U zitadel -d zitadel -c '\dn'يجب أن يعرض ذلك مخططاً باسم eventstore ومخططاً باسم projections. تعني القائمة الفارغة أن مرحلة الإعداد لم تصل إلى هذه النقطة، وسيوضح سجل حاوية API السبب.
المفتاح الرئيسي، وتكلفة فقدانه
يشفّر Zitadel الأسرار قبل تخزينها: أسرار العملاء، وبيانات اعتماد موفّري الهوية، وكلمة مرور SMTP، وبذور كلمات المرور لمرة واحدة، ومفاتيح الآلات. يفتح المفتاح الرئيسي كل هذه البيانات. يتكوّن من 32 محرفاً بالضبط، وتوضح الوثائق عاقبة فقدانه صراحةً: لا يمكن تغييره من دون فقدان الوصول إلى البيانات المشفّرة.
أنشئ مفتاحاً واستبدل سطر العنصر النائب في .env:
tr -dc A-Za-z0-9 </dev/urandom | head -c 32; echoحرّر سطر ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters بدلاً من إضافة سطر ثانٍ. يستخدم Compose التعريف الأخير للمفتاح المكرر، لذلك تنجح الإضافة، لكن وجود سطرين للمفتاح الرئيسي في الملف يسبب التباساً لمن يقرأه لاحقاً.
فكّر الآن في مكان وجود هذا المفتاح. يشغّل ملف Compose حاوية API كما يلي:
command: start-from-init --masterkey "${ZITADEL_MASTERKEY}"لذلك يوجد المفتاح الرئيسي في سطر أوامر الحاوية، حيث يعرض docker inspect قيمته لأي شخص يستطيع الوصول إلى Docker socket. على VPS يديره مسؤول واحد، تُعد هذه مقايضة مقبولة، وتكون الصلاحيات في .env هي التي تحمي المفتاح على القرص. إذا لم تكن هذه المقايضة مقبولة، فحمّل المفتاح كملف واستخدم --masterkeyFile /run/secrets/zitadel-masterkey بدلاً من ذلك، إذ يبقي القيمة خارج وسيطات العملية.
انسخ المفتاح الرئيسي إلى مدير كلمات المرور قبل التشغيل الأول. لا يظهر المفتاح في تفريغ قاعدة البيانات، ولذلك ينتج عن استعادة تفريغ باستخدام مفتاح رئيسي مختلف مثيل لا يستطيع قراءة أسراره الخاصة. احتفظ به في مكان مختلف عن الأرشيف الذي يحتوي على التفريغ، حتى لا تتضمن نسخة احتياطية مسروقة كلاً من البيانات المشفّرة والمفتاح اللازم لقراءتها.
حدّد النطاق الخارجي قبل التشغيل الأول
ZITADEL_DOMAIN في .env يمرّر القيمة إلى ZITADEL_EXTERNALDOMAIN داخل الحاوية، وهو الاسم الذي يكتبه المستخدمون. يستمد Zitadel منه مُصدر OIDC، والعنوان الأساسي لواجهة تسجيل الدخول، ونقاط نهاية SAML، واسم تسجيل دخول أول مسؤول. لذلك فهو ليس إعداداً شكلياً.
ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=trueيحدّد Zitadel المثيل الذي تتصل به من ترويسة Host. إذا لم تطابق هذه الترويسة نطاقاً معروفاً لديه، فستحصل كل الطلبات على الاستجابة نفسها:
ID=QUERY-1kIjX Message=Instance not foundهذا هو الخطأ الأكثر شيوعاً في Zitadel المستضاف ذاتياً، ويعني غالباً أحد أمرين. إما أن ZITADEL_DOMAIN ليس الاسم الذي تتصفحه، أو أن proxy أمامي يعيد كتابة Host إلى عنوان الخادم اللاحق. ويظهر الخطأ أيضاً عند التصفح باستخدام عنوان IP للخادم بدلاً من الاسم.
يمكنك تغيير هذه القيم لاحقاً. يجب على Zitadel إعادة تشغيل مرحلة الإعداد لالتقاط التغيير، وتحتفظ كل تطبيقاتك المسجّلة مسبقاً بعناوين إعادة التوجيه القديمة. اختيار الاسم النهائي الآن أقل تكلفة بكثير من تغييره لاحقاً.
إنهاء TLS باستخدام طبقة Let's Encrypt
بالنسبة إلى نطاق عام، أضف طبقة Let's Encrypt الخاصة بـZitadel. فهي تضبط Traefik لاستخدام تحدي HTTP الخاص بـACME (بيئة الإدارة التلقائية للشهادات)، وتستبدل المنافذ المنشورة بالمنفذين 80 و443، لذلك يجب ألا تستخدم أي خدمة أخرى على الخادم أيّاً منهما.
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/compose/docker-compose.mode-letsencrypt.yml
echo 'LETSENCRYPT_EMAIL=ops@example.com' >> .envتضبط الطبقة أيضاً ZITADEL_EXTERNALPORT: 443 وZITADEL_EXTERNALSECURE: true على حاوية API، ولذلك يتطابق عنوان URL العام مع عناوين URL التي ينشئها Zitadel لنفسه. يجب أن يُحل سجل A قبل البدء، لأن تحدي HTTP يفشل من دونه.
إذا كنت تنهي TLS مسبقاً على nginx أو على موازن تحميل، فاستخدم docker-compose.mode-external-tls.yml بدلاً من ذلك، واضبط TRAEFIK_TRUSTED_IPS على النطاقات التي يرسل منها الوكيل الخاص بك. لا يقبل Traefik رؤوس X-Forwarded-* إلا من العناوين الموجودة في تلك القائمة، ولذلك تعني القيمة الخاطئة إسقاط البروتوكول المُمرَّر، ثم يبدأ Zitadel بإنشاء عناوين http:// لموقع يستخدم HTTPS.
للوكيل الوسيط مهمتان يفرضهما Zitadel بدقة. يجب أن يتحدث HTTP/2 مع الواجهة الخلفية، لأن API يستخدم gRPC. ويجب أن يمرر Host دون تغيير، إلى جانب X-Forwarded-Proto: https. يوضّح مثال nginx الخاص بـZitadel البنية المطلوبة:
server {
listen 443 ssl;
http2 on;
ssl_certificate /etc/certs/selfsigned.crt;
ssl_certificate_key /etc/certs/selfsigned.key;
location /ui/v2/login {
proxy_pass http://login-external-tls:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}
location / {
grpc_pass grpc://zitadel-external-tls:8080;
grpc_set_header Host $host;
grpc_set_header X-Forwarded-Proto https;
}
}أسماء الواجهات الخلفية في المثال هي أسماء الحاويات في إعداد الاختبار الخاص بـZitadel، لذلك استبدلها بأسماء حاوياتك. إذا كنت تقدم Zitadel على منفذ غير 443، فاستخدم grpc_set_header Host $host:$server_port; لكي ينتقل المنفذ مع الرأس. أما بقية الإعداد فهي مضيف افتراضي عادي، ويشرح إعداد reverse proxy في nginx سطراً بسطر الأجزاء التي لا تخص Zitadel تحديداً.
المشرف الأول وفرض تغيير كلمة المرور
ينشئ التشغيل الأول مثيلاً واحداً، ومؤسسة واحدة، وحساب مشرف بشري واحد. يتكوّن اسم تسجيل الدخول من zitadel-admin@ ثم zitadel. ثم نطاقك الخارجي، ولذلك يكون مع ZITADEL_DOMAIN=auth.example.com كما يلي:
zitadel-admin@zitadel.auth.example.comتكون كلمة المرور هي Password1! ما لم تعيّن كلمة مرور خاصة بك. يفرض الإعداد الافتراضي في Zitadel تغيير كلمة المرور عند تسجيل الدخول الأول، لكن ملف Compose المرفق يتجاوز هذا الإعداد:
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: falseهذا السطر مضمّن مباشرةً في docker-compose.yml، ولا يُقرأ من .env. لذلك ضع قيمك الخاصة في ملف overlay صغير خاص بك. سمّه docker-compose.local.yml:
services:
zitadel-api:
environment:
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_ADDRESS: you@example.com
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: "a-long-temporary-password"
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "true"يحمّل Compose الملف docker-compose.override.yml تلقائياً فقط عند تشغيله من دون الخيار -f، بينما تمرّر كل أوامر دليل Zitadel الخيار -f، ما يؤدي إلى تعطيل ذلك. بدلاً من تكرار قائمة متزايدة من الخيارات، ثبّت قائمة الملفات في .env:
COMPOSE_FILE=docker-compose.yml:docker-compose.mode-letsencrypt.yml:docker-compose.local.ymlشغّله الآن:
docker compose pull
docker compose up -d --waitيحتفظ --wait بالأمر قيد التنفيذ حتى تنجح اختبارات الحالة. عندما لا تصل حاوية API إلى هذه المرحلة، يتوقف Compose مع dependency failed to start: container zitadel-compose-zitadel-api-1 is unhealthy، ويحتوي docker compose logs zitadel-api على السبب. في التشغيل الأول، يكون السبب عادةً طول masterkey أو DSN قاعدة البيانات.
سجّل الدخول إلى https://auth.example.com/ui/console، وغيّر كلمة المرور، ثم فعّل عاملاً ثانياً لهذا الحساب قبل إنشاء أي شيء آخر. تنطبق كل قيمة من قيم ZITADEL_FIRSTINSTANCE_* فقط أثناء إنشاء المثيل الأول. بعد إنشاء المثيل، لا يؤدي تعديلها إلى أي تغيير على الإطلاق.
لماذا لا تعمل إعادة تعيين كلمة المرور قبل إعداد SMTP
يكون موفّر الهوية الذي لا يستطيع إرسال البريد معطّلاً بطريقة قد تظل مخفية لأسابيع. يرسل Zitadel رسائل البريد لدعوات المستخدمين، والتحقق من العناوين، وروابط إعادة تعيين كلمة المرور، والرموز المستخدمة مرة واحدة، وإشعارات المطالبة بالنطاق. عند عدم إعداد موفّر SMTP، تظل Console تعرض الإجراء على أنه مكتمل، وتُرسل الرسالة إلى عامل الإشعارات من دون جهة يمكنه الإرسال إليها. تمنح الإعدادات الافتراضية ذلك العامل MaxAttempts: 3 وMaxTtl: 5m، لذلك يعيد المحاولة عدة مرات خلال بضع دقائق ثم يتوقف. ولا يخبر ذلك الشخص الذي ينتظر الرابط بأي شيء.
اضبطه في Console، ضمن إعدادات المثيل على https://auth.example.com/ui/console/settings. يطلب نموذج موفّر SMTP عنوان بريد المرسل، واسم المرسل، والمضيف والمنفذ، واسم المستخدم، وكلمة مرور SMTP، ومفتاح تبديل TLS. استخدم زر الاختبار في النموذج قبل الحفظ، لأنه يرسل رسالة فعلية: إما أن تصل أو لا تصل.
توجد مجموعة مقابلة من متغيرات البيئة، ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_HOST وما يماثلها. تُطبَّق هذه المتغيرات عند إنشاء مثيل. أما في مكدس قيد التشغيل، فلا تأثير لها، ولذلك تكون Console هي المكان الصحيح للمثيل الموجود.
هناك نقطتان تتعلقان بالتسليم من VPS، لأن الفشل يحدث عادةً هنا. يحظر معظم الموفّرين المنفذ الصادر 25 في الحسابات الجديدة، لذلك تنتهي محاولة الإرسال المباشر إلى خادم بريد المستلم بمهلة دون ظهور خطأ مفيد. استخدم relay موثّقاً على المنفذ 587 بدلاً من ذلك. وانشر سجلات SPF (sender policy framework) وDKIM (domainkeys identified mail) لنطاق الإرسال، وإلا فقد يصل رابط إعادة التعيين إلى مجلد البريد العشوائي، وهذا يبدو للمستخدم تماماً كما لو أن الرسالة لم تُرسل.
تحقق من ذلك قبل دعوة أي شخص. أنشئ مستخدماً مؤقتاً، واطلب إعادة تعيين كلمة المرور، وراقب وصول الرسالة. إذا لم تصل، فإن docker compose logs -f zitadel-api يحدّد فشل SMTP. تُخزَّن كلمة مرور SMTP مشفّرة في قاعدة البيانات، وهذا شيء إضافي يتولى masterkey حمايته.
انسخ Postgres وmasterkey احتياطياً وبشكل منفصل
كل ما يعرفه Zitadel موجود في PostgreSQL. ويفك masterkey تشفير هذه البيانات. خزّنهما في موقعين مختلفين.
ابدأ بإنشاء النسخة المفرغة:
sudo install -d -m 700 /srv/zitadel-backups
docker compose exec -T postgres \
pg_dump -U postgres -Fc zitadel > "/srv/zitadel-backups/zitadel-$(date +%F).dump"الخيار -Fc هو التنسيق المخصص. يضغط البيانات أثناء الإخراج، ويمكن لـpg_restore قراءتها بشكل انتقائي. أما exec -T فيلغي استخدام الطرفية، وهذا مهم لأن الأمر سيعمل من cron دون طرفية متصلة.
بعد ذلك، ادفع هذا المجلد إلى موقع خارجي باستخدام restic، الذي يشفّر البيانات ويزيل التكرار:
export RESTIC_REPOSITORY="sftp:backup@backup.example.com:/srv/restic/zitadel"
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /srv/zitadel-backups
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneيُشغَّل restic init مرة واحدة فقط، في اليوم الأول. ضع النسخة المفرغة والأمرين الأخيرين في /usr/local/bin/zitadel-backup.sh، وشغّلها كل ليلة:
0 3 * * * /usr/local/bin/zitadel-backup.shانسخ .env وكل ملفات compose التي تستخدمها احتياطياً داخل git. أما masterkey فهو الاستثناء من كل ذلك. خزّنه في مدير كلمات المرور وفي موقع ثانٍ لا يكون مستودع restic هذا، لأن الأرشيف الذي يحتوي على قاعدة البيانات ومفتاح فك تشفيرها معاً لا يعود نسخة احتياطية لنظام مشفّر.
النسخة الاحتياطية التي لم تستعدها ليست سوى تخمين. استعدها في قاعدة بيانات مؤقتة على الخادم نفسه وافحصها:
docker compose exec -T postgres createdb -U postgres zitadel_restore_test
docker compose exec -T postgres pg_restore -U postgres -d zitadel_restore_test \
< /srv/zitadel-backups/zitadel-2026-08-21.dump
docker compose exec -T postgres psql -U postgres -d zitadel_restore_test -c '\dt eventstore.*'
docker compose exec -T postgres dropdb -U postgres zitadel_restore_testيشير ظهور قائمة بالجداول في مخطط eventstore إلى أن النسخة المفرغة حقيقية. أما ظهور خطأ يفيد بعدم وجود المخطط فيعني أنها غير صالحة. وستكتشف ذلك في يوم لا يكلّفك فيه الفشل شيئاً. ينطبق النمط العام لنسخ مكدس Compose وترقيته هنا تقريباً دون تغيير، والجزء الوحيد الخاص بـZitadel هو إبقاء masterkey خارج الأرشيف نفسه.
ترقية Zitadel من دون فقدان المثيل
الترقية هي رفع رقم الإصدار في .env، يتبعه تنفيذ أمرين:
docker compose pull
docker compose up -d --waitافهم وظيفة الأمر الثاني قبل تنفيذه على مثيل يسجّل الأشخاص الدخول إليه. أمر الحاوية هو start-from-init، وهو يشغّل مرحلتي التهيئة والإعداد قبل بدء تقديم الخدمة. ومرحلة الإعداد هي التي تنفّذ ترحيلات قاعدة البيانات. لذلك يؤدي رفع الإصدار إلى تشغيل ترحيلات المخطط على قاعدة بياناتك الحية عند بدء الحاوية، من دون تدخل، بينما ينتظر --wait هناك نتيجة اختبار السلامة. لهذا السبب تحديداً لا يُعد اختبار الاستعادة المذكور أعلاه اختيارياً.
أنشئ تفريغاً حديثاً مباشرة قبل الترقية. أما تفريغ الليلة الماضية فهو شيء مختلف.
لا تتخطَّ إصداراً رئيسياً. يتطلب الانتقال من v3 إلى v4 استخدام v3.4.1 أو إصدار أحدث أولاً، لأن v4 أزال مفاتيح توقيع OIDC القديمة. لذلك تتوقف الرموز الموقعة بالمفاتيح القديمة عن اجتياز التحقق فور الانتقال. يشرح التنبيه التقني A-10017 من Zitadel هذه المشكلة، والحل هو تشغيل الإصدار الأحدث من v3 مدة كافية لانتهاء صلاحية الرموز القديمة قبل الترقية.
راقب مرحلة الإعداد باستخدام docker compose logs -f zitadel-api. تستغرق الترحيلات على eventstore كبير دقائق، ولن يوجّه Traefik الطلبات إلى API حتى ينجح اختبار السلامة. لذلك سيتوقف الموقع خلال هذه الفترة. خطط لذلك مسبقاً بدلاً من اكتشافه أثناء الترقية.
لا تعني الاستعادة التراجعية إعادة وضع الوسم القديم. بعد تنفيذ الترحيلات، لن يفهم البرنامج الثنائي الأقدم المخطط الذي يجده، ولذلك تتطلب الاستعادة التراجعية استعادة التفريغ. بعد أن يحتوي المثيل على مستخدمين فعليين، انتقل إلى docker-compose.prodlike.yml، وهي الطبقة التي تشغّل التهيئة والإعداد كخطوتين منفصلتين عن البدء. عندئذ تصبح الترحيلة عملية تشغّلها وتراقبها، بدلاً من أن تكون أثراً جانبياً لإعادة تشغيل الحاوية.
ما الذي تشير إليه إلى مزود الهوية الجديد
في Console، أنشئ مشروعاً ثم أنشئ تطبيقاً داخله. اختر OIDC لأي تكامل حديث، وسيمنحك Zitadel معرّف عميل وسر عميل ووثيقة اكتشاف على https://auth.example.com/.well-known/openid-configuration. تطلب معظم البرامج ذاتية الاستضافة التي تدعم تسجيل الدخول الأحادي هذه القيم تحديداً.
لا يدعم كثير من البرامج ذلك، أو لا يدعمه إلا ضمن فئة مدفوعة. في الحالة الأولى، يحوّل oauth2-proxy أمام التطبيق أي خدمة HTTP إلى خدمة يمكن لـZitadel حمايتها. وفي الحالة الثانية، تجدر قراءة كلفة SSO في التطبيقات ذاتية الاستضافة قبل أن تخطط لعملية ترحيل تعتمد على ميزة لم تدفع مقابلها.
FAQ
ما مقدار RAM وCPU الذي يحتاج إليه Zitadel المستضاف ذاتياً؟
يوصي دليل Zitadel للإنتاج بنحو 4 من أنوية CPU و8 GB من RAM لعقدة واحدة تعمل بإعداد مخفّض، وبـ16 GB لكل عقدة عند تفعيل التسجيل والمقاييس. ويُحدَّد PostgreSQL بشكل منفصل، بواقع نواة واحدة تقريباً لكل 100 طلب في الثانية و4 GB من RAM لكل نواة. يبدأ Compose quickstart ضمن 2 GB، وهذا يكفي لتجربته، لكنه أقل من الموارد التي يوصي بها المشروع لنظام تعتمد عليه خدمات أخرى.
ماذا يحدث إذا فقدت masterkey الخاصة بـZitadel؟
يبقى كل ما شُفِّر بها مشفّراً. لا يمكن فك تشفير أسرار العملاء، وبيانات اعتماد موفّري الهوية، وكلمة مرور SMTP، وبذور كلمات المرور لمرة واحدة، كما لا يمكن تغيير المفتاح لاحقاً. لا تكفي نسخة قاعدة البيانات وحدها لاستعادة instance عاملة، لأن النسخة تحتوي على ciphertext ولا تحتوي على المفتاح. خزّن masterkey في مدير كلمات مرور، وفي مكان منفصل عن النسخة الاحتياطية التي تحتوي على التفريغ. إذا فُقد كلاهما، فلا يبقى سوى إعادة إنشاء instance من الصفر.
لماذا لا تصل رسائل إعادة تعيين كلمة مرور Zitadel أبداً؟
لأنه لم يتم إعداد موفّر SMTP، أو لأن الموفّر الذي تم إعداده لا يستطيع التسليم. يضع Zitadel كل إشعار في قائمة انتظار لمعالج يحاول الإرسال 3 مرات افتراضياً، ويعرض النجاح في Console في كلتا الحالتين، لذلك يحدث الفشل بصمت. اضبط موفّر SMTP ضمن إعدادات instance، واستخدم زر الاختبار في ذلك النموذج، إذ يرسل رسالة فعلية. من VPS، استخدم relay موثّقاً على المنفذ 587، لأن معظم الموفّرين يحظرون المنفذ الصادر 25، وانشر سجلات SPF وDKIM لنطاق الإرسال حتى لا تُصنَّف الرسالة كبريد عشوائي.
هل يمكنني تغيير النطاق الخارجي لـZitadel بعد التثبيت؟
نعم، لكن ليس بتعديل .env وحده. غيّر ZITADEL_EXTERNALDOMAIN وZITADEL_EXTERNALPORT وZITADEL_EXTERNALSECURE، ثم دع Zitadel يعيد تشغيل مرحلة الإعداد حتى يلتقط التغيير. تحتفظ التطبيقات التي سجّلتها مسبقاً بعناوين URI القديمة لإعادة التوجيه، ويجب تحديثها يدوياً. كما سيُعاد Instance not found لكل طلب لا يتطابق رأس Host فيه مع نطاق يعرفه Zitadel. يؤدي اختيار الاسم النهائي قبل التشغيل الأول إلى تجنّب كل ذلك.