SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

آموزش نصب و میزبانی شخصی Zitadel روی VPS با Docker

برای اجرای بهینه Zitadel به 4 هسته CPU و 8 گیگابایت رم نیاز دارید. در این راهنما تنظیمات Postgres، masterkey، TLS، SMTP و استراتژی صحیح ارتقای دیتابیس را بررسی می‌کنیم.

پیش‌نیازهای میزبانی شخصی Zitadel روی یک VPS

برای میزبانی شخصی Zitadel روی یک VPS، به یک میزبان Docker، یک نام دامنه عمومی که به آن اشاره کند، PostgreSQL و حدود 4 هسته CPU با 8 گیگابایت رم نیاز دارید. Zitadel یک ارائه‌دهنده هویت (Identity Provider) است. این سرویس توکن‌ها را از طریق OIDC (OpenID Connect) و SAML (Security Assertion Markup Language) صادر می‌کند تا سایر سرویس‌های شما دیگر نیازی به نگهداری لیست کاربران اختصاصی خود نداشته باشند. نصب این سرویس شامل یک curl و یک docker compose up است. بخش‌هایی که تعیین‌کننده پایداری آن هستند عبارتند از: masterkey، کاربر پایگاه داده، SMTP (Simple Mail Transfer Protocol)، پشتیبان‌گیری و اولین ارتقا.

تمام موارد زیر فرض را بر این می‌گذارند که از Ubuntu 24.04، نسخه 24 یا جدیدتر Docker Engine به همراه پلاگین Compose استفاده می‌کنید و نامی مانند auth.example.com از قبل به سرور متصل (resolve) شده است.

Zitadel به چه میزان منابع VPS نیاز دارد؟

راهنمای شروع سریع Compose در مستندات Zitadel، مقدار 2 گیگابایت رم را پیشنهاد می‌دهد. این عدد برای یک لپ‌تاپ در نظر گرفته شده است. راهنمای عملیاتی (production) Zitadel ارقام متفاوتی را ارائه می‌دهد.

ChartZitadel's own published sizing guidance, August 2026
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 گیگابایت رم مصرف می‌کند. هسته‌های پردازشی برای هش کردن رمز عبور استفاده می‌شوند که فرآیندی عمدتاً کند است، بنابراین هجوم ورود کاربران باعث ایجاد جهش در مصرف CPU می‌شود. PostgreSQL نیمه دیگر این هزینه است: همان راهنما به ازای هر 100 درخواست در ثانیه، حدود یک هسته پردازشی و 4 گیگابایت رم به ازای هر هسته را در نظر می‌گیرد. با ترکیب این دو، به مقادیر 4 هسته و 8 گیگابایت رم می‌رسید که راهنما برای یک نود (node) پیشنهاد می‌دهد، یا در صورت فعال بودن لاگ‌ها و متریک‌ها، به 16 گیگابایت رم به ازای هر نود نیاز خواهید داشت.

بنابراین، یک VPS با 2 گیگابایت رم این پشته (stack) را اجرا می‌کند، اما این مقدار کمتر از چیزی است که پروژه برای محیط‌های واقعی توصیه می‌کند. سرویس احراز هویت، سرویسی است که سایر سرویس‌ها به آن وابسته‌اند. وقتی این سرویس از دسترس خارج شود، هیچ‌کدام از سرویس‌های وابسته اجازه ورود به کاربران را نمی‌دهند. تصمیم‌گیری در مورد اینکه 8 گیگابایت رم برای احراز هویت بیش از بودجه شماست، یک تصمیم منطقی است و اتخاذ آن در حال حاضر بسیار ارزان‌تر از پس از انجام مهاجرت است. مقایسه Keycloak، Authentik و Zitadel هزینه‌های حافظه و عملیاتی هر کدام را بررسی می‌کند و یک سرور Authentik خودمیزبان معمولاً پاسخ مناسب برای سرورهای کوچک‌تر است.

دریافت پشته و تعیین نسخه (Pin)

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 عمل می‌کند: درخواست‌ها را بر اساس مسیر هدایت کرده و با استفاده از overlay که در ادامه آمده، TLS (امنیت لایه انتقال) را مدیریت می‌کند. zitadel-api یک فایل باینری Go است که روی پورت 8080 اجرا می‌شود. zitadel-login رابط کاربری ورود است که در /ui/v2/login ارائه می‌شود. postgres همه چیز را در خود نگه می‌دارد. یک کش Redis و یک جمع‌آوری‌کننده OpenTelemetry نیز در همان فایل و پشت Compose profiles قرار دارند و تا زمانی که آن‌ها را فراخوانی نکنید، غیرفعال می‌مانند.

هنوز docker compose up را اجرا نکنید. اولین راه‌اندازی، instance را ایجاد می‌کند و چندین تنظیم در ادامه وجود دارد که پس از آن بدون انجام کارهای اضافی قابل تغییر نیستند.

فایل .env که کپی کرده‌اید، تگ‌های image مخصوص به خود را تعیین (pin) می‌کند:

ZITADEL_VERSION=v4.16.0
TRAEFIK_IMAGE=traefik:v3.7.7
POSTGRES_IMAGE=postgres:17.10-alpine

نسخه فعلی v4 برابر با v4.17.1 است که در 14 اوت 2026 منتشر شده است. مقدار ZITADEL_VERSION را روی نسخه‌ای که قصد اجرای آن را دارید تنظیم کنید و به جای دنبال کردن هر نسخه جدیدی، روی خط v4 باقی بمانید. فایل curl در بالا، docker-compose.yml را از شاخه main دریافت می‌کند که به هیچ نسخه‌ای محدود نشده است؛ بنابراین کپی هر دو فایل خود را در یک مخزن git ذخیره (commit) کنید. در غیر این صورت، اجرای همان دستور روی یک سرور جدید در ماه آینده، فایل متفاوتی به شما می‌دهد و متوجه نخواهید شد چه چیزی تغییر کرده است.

ایجاد یک کاربر اختصاصی و رمز عبور واقعی برای Postgres

نسخه پیش‌فرض .env، برنامه Zitadel را با استفاده از کاربر superuser و رمز عبور postgres به PostgreSQL متصل می‌کند:

POSTGRES_ADMIN_USER=postgres
POSTGRES_ADMIN_PASSWORD=postgres
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://postgres:postgres@postgres:5432/zitadel?sslmode=disable

در مرحله امن‌سازی (hardening) یک تله وجود دارد. مستندات Zitadel به شما می‌گویند که POSTGRES_ZITADEL_PASSWORD را به .env اضافه کنید، اما docker-compose.yml پایه هرگز این متغیر را نمی‌خواند؛ بنابراین تنظیم آن هیچ تغییری ایجاد نمی‌کند. تغییر دادن POSTGRES_ADMIN_PASSWORD به تنهایی باعث قطع اتصال می‌شود، زیرا رمز عبور به‌صورت متنی (literal) در رشته DSN (نام منبع داده) نیز نوشته شده است. DSN همان خطی است که نحوه اتصال Zitadel را تعیین می‌کند.

توضیحات موجود در .env.example موضوع را به‌وضوح بیان کرده‌اند: هنگامی که یک DSN پیکربندی می‌شود، Zitadel مستقیماً از همان کاربر استفاده می‌کند و یک کاربر با دسترسی محدود برای شما نمی‌سازد؛ بنابراین این نقش (role) باید پیش از اولین اجرا وجود داشته باشد. یک رمز عبور تولید کنید، 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 در داخل کانتینر و از طریق سوکت محلی آن اجرا می‌شوند که ایمیج رسمی Postgres به آن اعتماد دارد، بنابراین نیازی به وارد کردن رمز عبور نیست. مالکیت (Ownership) بخش مهم ماجراست. در PostgreSQL نسخه 15 و جدیدتر، یک GRANT ALL PRIVILEGES ON DATABASE ساده دیگر به نقش اجازه نمی‌دهد در اسکیما (schema)ی public جدول ایجاد کند؛ در نتیجه مرحله راه‌اندازی Zitadel هنگام ساخت اسکیماها با خطای مجوز مواجه می‌شود. تعیین کردن این نقش به‌عنوان مالک دیتابیس و اسکیما، از بروز این مشکل جلوگیری می‌کند.

اکنون DSN را به سمت نقش جدید هدایت کنید و در حالی که فایل را ویرایش می‌کنید، یک رمز عبور مدیریتی واقعی تنظیم نمایید:

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 در دسترس است و پورت آن هرگز روی هاست منتشر (publish) نمی‌شود. پس از اولین اجرای کامل، بررسی کنید که آیا نقش واقعاً مالک داده‌های خود است یا خیر:

docker compose exec -T postgres psql -U zitadel -d zitadel -c '\dn'

این دستور باید یک اسکیما با نام eventstore و یک اسکیما با نام projections را فهرست کند. خالی بودن لیست به این معنی است که مرحله راه‌اندازی هرگز به آنجا نرسیده است و لاگ کانتینر API علت آن را مشخص خواهد کرد.

کلید اصلی (masterkey) و هزینه از دست دادن آن

Zitadel پیش از ذخیره‌سازی اسرار، آن‌ها را رمزنگاری می‌کند: کلاینت‌سکرت‌ها، اعتبارنامه‌های ارائه‌دهنده هویت، رمز عبور SMTP، سیدهای رمز یک‌بارمصرف (OTP) و کلیدهای ماشین. کلید اصلی (masterkey) قفل تمام این موارد را باز می‌کند. این کلید دقیقاً 32 کاراکتر است و مستندات به‌صراحت درباره پیامد آن هشدار می‌دهند: امکان تغییر آن بدون از دست دادن دسترسی به داده‌های رمزنگاری‌شده وجود ندارد.

یک کلید تولید کنید و آن را جایگزین خط جای‌گیر در .env کنید:

tr -dc A-Za-z0-9 </dev/urandom | head -c 32; echo

به‌جای افزودن یک خط دوم، خط ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters را ویرایش کنید. Docker Compose آخرین تعریف از یک کلید تکراری را در نظر می‌گیرد؛ بنابراین افزودن خط جدید کار می‌کند، اما وجود دو خط masterkey در یک فایل، برای کسی که در آینده آن را می‌خواند، یک تله محسوب می‌شود.

اکنون به محل نگهداری این کلید فکر کنید. فایل Compose کانتینر API را به این صورت اجرا می‌کند:

command: start-from-init --masterkey "${ZITADEL_MASTERKEY}"

بنابراین کلید اصلی در خط فرمان کانتینر قرار می‌گیرد، جایی که docker inspect آن را برای هر کسی که به Docker socket دسترسی داشته باشد، نمایش می‌دهد. در یک VPS با یک مدیر سیستم، این یک مصالحه قابل‌قبول است و حالت (mode) فایل .env همان چیزی است که از آن روی دیسک محافظت می‌کند. اگر این وضعیت قابل‌قبول نیست، کلید را به‌عنوان یک فایل mount کنید و به‌جای آن از --masterkeyFile /run/secrets/zitadel-masterkey استفاده کنید که مقدار کلید را از آرگومان‌های پردازش دور نگه می‌دارد.

پیش از اولین اجرا، کلید اصلی را در مدیریت رمز عبور (password manager) خود کپی کنید. این کلید در خروجی dump دیتابیس ظاهر نمی‌شود؛ بنابراین اگر یک dump را با کلید اصلی متفاوتی بازیابی کنید، نمونه‌ای ایجاد می‌شود که قادر به خواندن اسرار خود نیست. آن را در جایی غیر از آرشیوی که dump را در خود نگه می‌دارد ذخیره کنید تا در صورت سرقت یک نسخه پشتیبان، هم داده‌های رمزنگاری‌شده و هم کلید دسترسی به آن‌ها یکجا لو نرود.

تنظیم دامنه خارجی پیش از اولین اجرا

ZITADEL_DOMAIN در .env مقدار ZITADEL_EXTERNALDOMAIN را در کانتینر تغذیه می‌کند و این همان نامی است که کاربران شما تایپ می‌کنند. Zitadel صادرکننده OIDC، آدرس پایه رابط ورود، نقاط پایانی SAML و نام کاربری اولین مدیر سیستم را از این مقدار استخراج می‌کند، بنابراین این یک تنظیم ظاهری نیست.

ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=true

Zitadel تشخیص می‌دهد که شما با کدام نمونه (instance) در حال تعامل هستید، این کار از طریق هدر Host انجام می‌شود. اگر این هدر با دامنه‌ای که Zitadel می‌شناسد مطابقت نداشته باشد، تمام درخواست‌ها پاسخ یکسانی دریافت می‌کنند:

ID=QUERY-1kIjX Message=Instance not found

این رایج‌ترین خطا در میزبانی شخصی (self-hosted) Zitadel است و تقریباً همیشه به یکی از دو مورد زیر اشاره دارد. یا ZITADEL_DOMAIN نامی نیست که شما در مرورگر وارد کرده‌اید، یا یک پروکسی در لایه جلویی، مقدار Host را به آدرس بالادستی (upstream) بازنویسی می‌کند. وارد کردن آدرس IP سرور در مرورگر به‌جای نام دامنه نیز همین خطا را ایجاد می‌کند.

شما می‌توانید این مقادیر را بعداً تغییر دهید. Zitadel برای اعمال تغییرات باید مرحله راه‌اندازی (setup) خود را دوباره اجرا کند و تمام برنامه‌هایی که قبلاً ثبت کرده‌اید، همچنان از URIهای تغییر مسیر (redirect URI) قدیمی خود استفاده خواهند کرد. انتخاب نام نهایی در همین مرحله، بسیار کم‌هزینه‌تر از تغییر آن در آینده است.

خاتمه TLS با استفاده از overlay مربوط به Let's Encrypt

برای یک دامنه عمومی، overlay مربوط به Let's Encrypt در Zitadel را اضافه کنید. این کار Traefik را به چالش 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

این overlay همچنین ZITADEL_EXTERNALPORT: 443 و ZITADEL_EXTERNALSECURE: true را روی کانتینر API تنظیم می‌کند؛ به همین دلیل است که URL عمومی و URLهایی که Zitadel برای خود می‌سازد، با هم مطابقت دارند. رکورد A باید پیش از شروع کار resolve شده باشد، زیرا چالش HTTP بدون آن با شکست مواجه می‌شود.

اگر در حال حاضر خاتمه TLS را روی nginx یا یک load balancer انجام می‌دهید، از docker-compose.mode-external-tls.yml استفاده کنید و TRAEFIK_TRUSTED_IPS را روی محدوده‌هایی تنظیم کنید که پروکسی شما از آن‌ها درخواست ارسال می‌کند. Traefik تنها هدرهای X-Forwarded-* را از آدرس‌های موجود در آن لیست می‌پذیرد؛ بنابراین مقدار نادرست باعث می‌شود پروتکل ارسالی نادیده گرفته شود و Zitadel شروع به ساخت URLهای http:// برای یک سایت HTTPS کند.

یک پروکسی بالادستی (upstream) دو وظیفه دارد که Zitadel در مورد آن‌ها سخت‌گیر است. پروکسی باید با backend به صورت 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;
    }
}

نام‌های upstream در آنجا، کانتینرهای موجود در محیط تست Zitadel هستند، بنابراین آن‌ها را با نام‌های خود جایگزین کنید. اگر Zitadel را روی پورتی غیر از 443 ارائه می‌دهید، از grpc_set_header Host $host:$server_port; استفاده کنید تا پورت همراه با هدر منتقل شود. باقی موارد یک virtual host معمولی است و پیکربندی reverse proxy در nginx خط به خط بخش‌هایی را که مختص Zitadel نیستند، پوشش می‌دهد.

اولین مدیر و اجبار به تغییر رمز عبور

اولین اجرا، یک instance، یک سازمان و یک مدیر انسانی ایجاد می‌کند. نام کاربری ورود، ترکیبی از 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 به صورت hardcode قرار دارد و از .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"

Docker 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 دستور را تا زمانی که healthcheckها با موفقیت انجام شوند، نگه می‌دارد. اگر container مربوط به 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 وارد شوید، رمز عبور را تغییر دهید و سپس پیش از ایجاد هر چیز دیگری، احراز هویت دو مرحله‌ای (2FA) را برای آن حساب فعال کنید. هر مقدار ZITADEL_FIRSTINSTANCE_* تنها در زمان ایجاد اولین instance اعمال می‌شود. پس از اینکه instance ایجاد شد، ویرایش این مقادیر هیچ تأثیری نخواهد داشت.

چرا بازنشانی رمز عبور تا زمانی که SMTP کار نکند، بی‌اثر است

یک ارائه‌دهنده هویت (identity provider) که نمی‌تواند ایمیل ارسال کند، دچار نقصی است که ممکن است هفته‌ها پنهان بماند. Zitadel برای دعوت از کاربران، تأیید آدرس، لینک‌های بازنشانی رمز عبور، کدهای یک‌بارمصرف و اعلان‌های مالکیت دامنه، ایمیل ارسال می‌کند. بدون پیکربندی یک ارائه‌دهنده SMTP، کنسول همچنان گزارش می‌دهد که عملیات انجام شده است، اما پیام به یک notification worker ارسال می‌شود که مقصدی برای فرستادن آن ندارد. تنظیمات پیش‌فرض به آن worker مقادیر MaxAttempts: 3 و MaxTtl: 5m را می‌دهد، بنابراین پس از چند بار تلاش در عرض چند دقیقه، متوقف می‌شود. هیچ پیامی به شخصی که منتظر لینک است، ارسال نمی‌شود.

آن را در کنسول، در بخش تنظیمات instance در https://auth.example.com/ui/console/settings پیکربندی کنید. فرم ارائه‌دهنده SMTP از شما آدرس ایمیل فرستنده، نام فرستنده، host و port، نام کاربری، رمز عبور SMTP و گزینه TLS را می‌خواهد. پیش از ذخیره، از دکمه تست در همان فرم استفاده کنید، زیرا یک پیام واقعی ارسال می‌کند: یا پیام می‌رسد یا نمی‌رسد.

مجموعه‌ای از متغیرهای محیطی متناظر، یعنی ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_HOST و موارد مشابه آن وجود دارند. این متغیرها هنگام ایجاد یک instance اعمال می‌شوند. در پشته‌ای (stack) که از قبل در حال اجراست، این متغیرها اثری ندارند، بنابراین کنسول مکان مناسبی برای یک instance موجود است.

دو نکته درباره ارسال ایمیل از یک VPS وجود دارد، زیرا معمولاً مشکل از همین‌جا ناشی می‌شود. اکثر ارائه‌دهندگان، پورت 25 خروجی را در حساب‌های جدید مسدود می‌کنند، بنابراین ارسال مستقیم به سرور ایمیل گیرنده با timeout مواجه شده و خطای مفیدی ارائه نمی‌دهد. به‌جای آن از یک relay احراز هویت‌شده روی پورت 587 استفاده کنید. همچنین رکوردهای SPF (مخفف sender policy framework) و DKIM (مخفف domainkeys identified mail) را برای دامنه فرستنده منتشر کنید؛ در غیر این صورت، لینک بازنشانی در پوشه spam قرار می‌گیرد که از دید کاربر دقیقاً مشابه حالتی است که ایمیل هرگز ارسال نشده باشد.

پیش از دعوت از هر کسی، آن را آزمایش کنید. یک کاربر موقت بسازید، درخواست بازنشانی رمز عبور بدهید و رسیدن پیام را بررسی کنید. اگر پیام نرسید، docker compose logs -f zitadel-api علت شکست SMTP را مشخص می‌کند. رمز عبور SMTP به‌صورت رمزنگاری‌شده در دیتابیس ذخیره می‌شود، که این یکی دیگر از مواردی است که masterkey از آن محافظت می‌کند.

پشتیبان‌گیری جداگانه از Postgres و masterkey

هر آنچه Zitadel می‌داند در PostgreSQL ذخیره شده است. ابزاری که این داده‌ها را رمزگشایی می‌کند، masterkey است. از این دو در دو مکان متفاوت پشتیبان تهیه کنید.

ابتدا dump را بگیرید:

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 به خارج از سایت منتقل کنید، که داده‌ها را رمزنگاری و deduplicate می‌کند:

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 فقط یک‌بار و در روز اول اجرا می‌شود. dump و دو دستور آخر را در /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 به این معنی است که dump واقعی است. خطایی مبنی بر عدم وجود طرحواره به این معنی است که dump معتبر نیست، و شما این موضوع را در روزی متوجه شده‌اید که هزینه‌ای برایتان ندارد. الگوی کلی برای پشتیبان‌گیری و ارتقای یک استک Compose تقریباً بدون تغییر در اینجا صدق می‌کند و نگهداری masterkey خارج از همان آرشیو، تنها بخش اختصاصی مربوط به Zitadel است.

ارتقای Zitadel بدون از دست دادن instance

ارتقا شامل تغییر نسخه در .env و سپس اجرای دو دستور زیر است:

docker compose pull
docker compose up -d --wait

پیش از اجرای دستور دوم روی سرویسی که کاربران از آن استفاده می‌کنند، عملکرد آن را درک کنید. دستور کانتینر start-from-init است که مراحل init و setup را پیش از شروع سرویس‌دهی اجرا می‌کند؛ مرحله setup همان مهاجرت‌های پایگاه داده (database migrations) است. بنابراین، تغییر نسخه باعث می‌شود مهاجرت‌های شمای پایگاه داده به‌صورت خودکار و در لحظه شروع کانتینر روی پایگاه داده زنده شما اجرا شوند، در حالی که --wait منتظر موفقیت healthcheck می‌ماند. به همین دلیل است که تست بازیابی (restore test) که پیش‌تر ذکر شد، اختیاری نیست.

بلافاصله پیش از ارتقا، یک dump تازه تهیه کنید. dump دیشب، وضعیت فعلی را پوشش نمی‌دهد.

از یک نسخه اصلی (major version) به نسخه اصلی بعدی نپرید. انتقال از v3 به v4 مستلزم این است که ابتدا روی نسخه v3.4.1 یا بالاتر باشید، زیرا در v4 کلیدهای امضای OIDC قدیمی حذف شده‌اند و توکن‌هایی که با کلیدهای قدیمی امضا شده‌اند، بلافاصله پس از ارتقا دیگر معتبر نخواهند بود. توصیه فنی Zitadel با شناسه A-10017 این موضوع را شرح می‌دهد و راه‌حل آن، اجرای نسخه v3 جدیدتر برای مدتی است تا توکن‌های قدیمی منقضی شوند و سپس ارتقا انجام شود.

مرحله setup را با docker compose logs -f zitadel-api مانیتور کنید. مهاجرت‌ها در یک eventstore بزرگ ممکن است چند دقیقه طول بکشد و Traefik تا زمانی که healthcheck پاس نشود، ترافیک را به API هدایت نمی‌کند؛ بنابراین سایت در این بازه زمانی از دسترس خارج خواهد بود. این فرآیند را از پیش برنامه‌ریزی کنید تا با قطعی ناگهانی مواجه نشوید.

بازگشت به نسخه قبل (Rollback) صرفاً با بازگرداندن تگ قدیمی امکان‌پذیر نیست. هنگامی که مهاجرت‌ها اجرا شدند، باینری قدیمی‌تر، شمای جدید پایگاه داده را نمی‌شناسد؛ بنابراین بازگشت به نسخه قبل به معنای بازیابی (restore) همان dump است. هنگامی که instance شما دارای کاربران واقعی است، از docker-compose.prodlike.yml استفاده کنید؛ این overlay مراحل init و setup را از مرحله start جدا می‌کند تا مهاجرت به عملیاتی تبدیل شود که خودتان آن را آغاز و نظارت می‌کنید، نه یک اثر جانبی از ری‌استارت کانتینر.

چه چیزی را باید به ارائه‌دهنده هویت جدید خود معرفی کنید

در Console، یک پروژه ایجاد کنید و سپس یک application درون آن بسازید. برای هر سرویس مدرنی گزینه OIDC را انتخاب کنید؛ Zitadel به شما یک client ID، یک client secret و یک discovery document در https://auth.example.com/.well-known/openid-configuration ارائه می‌دهد. اکثر نرم‌افزارهای self-hosted که از single sign-on پشتیبانی می‌کنند، دقیقاً به همین موارد نیاز دارند.

بسیاری از نرم‌افزارها از این قابلیت پشتیبانی نمی‌کنند یا آن را فقط در نسخه پولی ارائه می‌دهند. برای حالت اول، استفاده از oauth2-proxy در مقابل برنامه هر سرویس HTTP را به چیزی تبدیل می‌کند که Zitadel می‌تواند از آن محافظت کند. برای حالت دوم، خواندن مطلب هزینه SSO در برنامه‌های self-hosted پیش از آنکه مهاجرت خود را حول قابلیتی که برای آن پولی پرداخت نکرده‌اید برنامه‌ریزی کنید، ارزشمند است.

FAQ

Zitadel برای میزبانی شخصی به چه میزان RAM و CPU نیاز دارد؟

راهنمای عملیاتی Zitadel برای یک نود با تنظیمات کاهش‌یافته، حدود 4 هسته CPU و 8 گیگابایت RAM، و برای هر نود با فعال بودن لاگ‌ها و متریک‌ها 16 گیگابایت RAM توصیه می‌کند. منابع مورد نیاز برای PostgreSQL جداگانه محاسبه می‌شود؛ تقریباً یک هسته به ازای هر 100 درخواست در ثانیه و 4 گیگابایت RAM به ازای هر هسته. نسخه سریع Docker Compose با کمتر از 2 گیگابایت RAM اجرا می‌شود که برای تست مناسب است، اما این مقدار کمتر از توصیه‌های پروژه برای سیستمی است که سایر سرویس‌ها به آن وابسته‌اند.

اگر masterkey مربوط به Zitadel را گم کنم چه می‌شود؟

هر چیزی که با آن رمزنگاری شده باشد، رمزنگاری‌شده باقی می‌ماند. کلاینت‌سکرت‌ها، اعتبارنامه‌های ارائه‌دهنده هویت، رمز عبور SMTP و سیدهای رمز یک‌بارمصرف (OTP) قابل رمزگشایی نیستند و کلید نیز پس از تنظیم اولیه قابل تغییر نیست. یک فایل dump از پایگاه داده به‌تنهایی برای بازیابی یک نمونه فعال کافی نیست، زیرا dump فقط شامل متن رمزنگاری‌شده است و کلیدی در آن وجود ندارد. masterkey را در یک مدیریت‌کننده رمز عبور و در مکانی جدا از نسخه پشتیبانِ حاوی dump نگهداری کنید. اگر هر دو از بین بروند، تنها راه باقی‌مانده، بازسازی کامل نمونه از ابتدا است.

چرا ایمیل‌های بازنشانی رمز عبور Zitadel هرگز نمی‌رسند؟

به این دلیل که یا هیچ ارائه‌دهنده SMTP پیکربندی نشده است، یا ارائه‌دهنده تنظیم‌شده قادر به ارسال نیست. Zitadel به‌صورت پیش‌فرض هر اعلان را در صف یک worker با 3 تلاش قرار می‌دهد و در هر صورت در کنسول وضعیت را موفقیت‌آمیز گزارش می‌کند، بنابراین خطا به‌صورت خاموش رخ می‌دهد. ارائه‌دهنده SMTP را در تنظیمات نمونه پیکربندی کنید و از دکمه تست در همان فرم استفاده کنید که یک پیام واقعی ارسال می‌کند. از روی یک VPS، از یک relay احراز هویت‌شده روی پورت 587 استفاده کنید، زیرا اکثر ارائه‌دهندگان پورت 25 خروجی را مسدود می‌کنند. همچنین رکوردهای SPF و DKIM را برای دامنه فرستنده منتشر کنید تا ایمیل‌ها به‌عنوان اسپم فیلتر نشوند.

آیا می‌توانم دامنه خارجی Zitadel را پس از نصب تغییر دهم؟

بله، اما نه فقط با ویرایش .env. مقادیر ZITADEL_EXTERNALDOMAIN، ZITADEL_EXTERNALPORT و ZITADEL_EXTERNALSECURE را تغییر دهید و سپس اجازه دهید Zitadel مرحله setup خود را دوباره اجرا کند تا تغییرات را اعمال کند. برنامه‌هایی که قبلاً ثبت کرده‌اید، URIهای تغییر مسیر (redirect URI) قدیمی خود را حفظ می‌کنند و باید به‌صورت دستی به‌روزرسانی شوند. همچنین هر درخواستی که هدر Host آن با دامنه‌ای که Zitadel می‌شناسد مطابقت نداشته باشد، پاسخ Instance not found دریافت می‌کند. انتخاب نام نهایی پیش از اولین اجرا، از تمام این مشکلات جلوگیری می‌کند.