কিভাবে একটি VPS-এ Docker দিয়ে Zitadel self-host করবেন
একটি VPS-এ Zitadel self-host করার পূর্ণাঙ্গ গাইড। 4টি CPU core ও 8 GB RAM-এর প্রয়োজনীয়তা, PostgreSQL কনফিগারেশন, masterkey সেটআপ এবং ডাটাবেস আপগ্রেড করার সঠিক নিয়ম জানুন।
একটি VPS-এ Zitadel self-host করার জন্য যা প্রয়োজন
একটি VPS-এ Zitadel self-host করার জন্য আপনার একটি Docker host, সেটির দিকে নির্দেশ করে এমন একটি public DNS name, PostgreSQL এবং প্রায় 4টি CPU core ও 8 GB RAM প্রয়োজন। Zitadel একটি identity provider। এটি OIDC (OpenID Connect) এবং SAML (security assertion markup language)-এর মাধ্যমে token ইস্যু করে, যাতে আপনার অন্যান্য service-গুলোকে আলাদাভাবে user list সংরক্ষণ করতে না হয়। এর ইনস্টলেশন প্রক্রিয়ায় একটি curl এবং একটি docker compose up অন্তর্ভুক্ত থাকে। যে বিষয়গুলো এর স্থায়িত্ব নির্ধারণ করে তা হলো masterkey, database user, SMTP (simple mail transfer protocol), backup এবং প্রথম upgrade।
নিচের সবকিছু Ubuntu 24.04, Docker Engine 24 বা তার পরবর্তী সংস্করণ (Compose plugin সহ) এবং auth.example.com-এর মতো একটি নাম যা ইতিমধ্যে সার্ভারের সাথে যুক্ত হয়েছে, তার ওপর ভিত্তি করে লেখা হয়েছে।
Zitadel-এর জন্য কতটুকু VPS প্রয়োজন?
Zitadel-এর ডকুমেন্টেশনে থাকা Compose quickstart-এ 2 GB RAM-এর কথা বলা হয়েছে। এই সংখ্যাটি মূলত ল্যাপটপের জন্য। 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 কোরগুলো মূলত পাসওয়ার্ড হ্যাশিংয়ের জন্য, যা ইচ্ছাকৃতভাবে ধীরগতির করা হয়েছে, তাই একসাথে অনেক লগইন আসলে CPU-তে চাপ পড়ে। PostgreSQL হলো খরচের অন্য অর্ধেক: একই গাইডে প্রতি 100 রিকোয়েস্ট প্রতি সেকেন্ডের জন্য একটি কোর এবং প্রতি কোরের জন্য 4 GB RAM বরাদ্দ করার কথা বলা হয়েছে। এই দুটিকে একসাথে করলে আপনি গাইডে উল্লিখিত একটি নোডের জন্য 4 কোর এবং 8 GB RAM-এর হিসেবে পৌঁছাবেন, অথবা লগিং ও মেট্রিক্স চালু থাকলে প্রতি নোডে 16 GB RAM প্রয়োজন হবে।
সুতরাং, একটি 2 GB VPS এই স্ট্যাকটি চালু করতে পারবে, তবে এটি বাস্তব কোনো ব্যবহারের জন্য প্রজেক্টের সুপারিশকৃত পরিমাণের চেয়ে কম। লগইন এমন একটি সার্ভিস যার ওপর অন্য সব সার্ভিস নির্ভর করে। এটি ডাউন থাকলে, এর ওপর নির্ভরশীল কোনো সার্ভিসই কাউকে প্রবেশ করতে দেবে না। অথেন্টিকেশনের পেছনে 8 GB RAM খরচ করতে না চাওয়া একটি যৌক্তিক সিদ্ধান্ত, এবং মাইগ্রেশনের পরে এটি বোঝার চেয়ে এখনই সিদ্ধান্ত নেওয়া অনেক সাশ্রয়ী। Keycloak, Authentik এবং Zitadel-এর তুলনা অংশে প্রতিটি সার্ভিসের মেমরি ও অপারেশনাল কাজের খরচ আলোচনা করা হয়েছে, এবং একটি self-hosted 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 হলো রিভার্স প্রক্সি: এটি পাথ অনুযায়ী রিকোয়েস্ট রাউট করে এবং নিচের ওভারলে-এর মাধ্যমে TLS (ট্রান্সপোর্ট লেয়ার সিকিউরিটি) টার্মিনেট করে। zitadel-api হলো পোর্ট 8080-এ থাকা Go বাইনারি। 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 আগস্ট 2026 তারিখে প্রকাশিত হয়েছে। আপনি যে ভার্সনটি চালাতে চান তা ZITADEL_VERSION-এ সেট করুন এবং নতুন কোনো ভার্সন ট্র্যাক না করে v4 লাইনেই থাকুন। উপরের curl ফাইলটি main ব্রাঞ্চ থেকে docker-compose.yml পুল করে, যা কোনো কিছুর সাথে পিন করা নেই। তাই ফাইল দুটির আপনার কপি একটি git রিপোজিটরিতে কমিট করে রাখুন। অন্যথায়, আগামী মাসে নতুন কোনো বক্সে একই কমান্ড চালালে আপনি ভিন্ন একটি ফাইল পাবেন এবং কী পরিবর্তন হয়েছে তা আপনি জানতে পারবেন না।
Postgres-এর জন্য আলাদা ইউজার এবং একটি শক্তিশালী পাসওয়ার্ড সেট করা
Zitadel-এর সাথে আসা .env ফাইলটি PostgreSQL-এর সুপারইউজার হিসেবে Zitadel-কে সংযুক্ত করে, যার পাসওয়ার্ড হলো postgres:
POSTGRES_ADMIN_USER=postgres
POSTGRES_ADMIN_PASSWORD=postgres
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://postgres:postgres@postgres:5432/zitadel?sslmode=disableএখানে হার্ডেনিং বা নিরাপত্তা নিশ্চিত করার ধাপে একটি ফাঁদ রয়েছে। Zitadel-এর ডকুমেন্টেশন অনুযায়ী আপনাকে .env ফাইলে POSTGRES_ZITADEL_PASSWORD যুক্ত করতে বলা হয়, কিন্তু মূল docker-compose.yml ফাইলটি এই ভেরিয়েবলটি কখনোই পড়ে না, তাই এটি পরিবর্তন করলে কোনো কাজ হয় না। শুধুমাত্র POSTGRES_ADMIN_PASSWORD পরিবর্তন করলে সংযোগ বিচ্ছিন্ন হয়ে যায়, কারণ পাসওয়ার্ডটি DSN (data source name) স্ট্রিংয়ের ভেতরে সরাসরি লেখা থাকে। 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 কলগুলো কন্টেইনারের ভেতরে লোকাল সকেটের মাধ্যমে চলে, যা অফিসিয়াল Postgres ইমেজ বিশ্বাস করে, তাই এগুলো পাসওয়ার্ড চায় না। এখানে মালিকানা বা ownership গুরুত্বপূর্ণ। PostgreSQL 15 এবং তার পরবর্তী ভার্সনগুলোতে সাধারণ GRANT ALL PRIVILEGES ON DATABASE কমান্ড দিয়ে এখন আর কোনো রোল 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 নেটওয়ার্কের মাধ্যমেই অ্যাক্সেস করা যায় এবং এর পোর্ট কখনোই হোস্টের জন্য উন্মুক্ত করা হয় না। প্রথমবার পুরোপুরি স্টার্ট হওয়ার পর, যাচাই করুন যে রোলটি সত্যিই তার ডাটার মালিক কি না:
docker compose exec -T postgres psql -U zitadel -d zitadel -c '\dn'এটি একটি eventstore স্কিমা এবং একটি projections স্কিমা প্রদর্শন করবে। যদি তালিকাটি খালি থাকে, তবে বুঝতে হবে সেটআপ প্রক্রিয়াটি সেই পর্যন্ত পৌঁছাতে পারেনি, এবং API কন্টেইনারের লগ ফাইল দেখলে এর কারণ জানা যাবে।
মাস্টার-কি এবং এটি হারিয়ে ফেলার পরিণাম
Zitadel ডেটা সংরক্ষণ করার আগে গোপন তথ্যগুলো এনক্রিপ্ট করে নেয়: client secrets, identity provider credentials, SMTP password, one-time-password seeds এবং machine keys। মাস্টার-কি এই সবকিছু আনলক করে। এটি ঠিক 32 অক্ষরের একটি কি এবং ডকুমেন্টেশনে এর পরিণাম সম্পর্কে স্পষ্টভাবে বলা হয়েছে: এনক্রিপ্ট করা ডেটার অ্যাক্সেস না হারিয়ে এটি পরিবর্তন করা সম্ভব নয়।
একটি কি তৈরি করুন এবং .env-এ থাকা প্লেসহোল্ডার লাইনটি প্রতিস্থাপন করুন:
tr -dc A-Za-z0-9 </dev/urandom | head -c 32; echoZITADEL_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 issuer, লগইন ইন্টারফেসের বেস URI, SAML এন্ডপয়েন্ট এবং প্রথম অ্যাডমিনের লগইন নাম নির্ধারণ করে, তাই এটি কেবল নামমাত্র কোনো বিষয় নয়।
ZITADEL_DOMAIN=auth.example.com
ZITADEL_EXTERNALPORT=443
ZITADEL_EXTERNALSECURE=trueZitadel Host হেডার থেকে বুঝতে পারে আপনি কোন ইনস্ট্যান্সের সাথে কথা বলছেন। যদি সেই হেডারটি এমন কোনো ডোমেইনের সাথে না মেলে যা Zitadel চেনে, তবে প্রতিটি অনুরোধের উত্তরে একই ফলাফল আসবে:
ID=QUERY-1kIjX Message=Instance not foundএটি সেলফ-হোস্টেড Zitadel-এর সবচেয়ে সাধারণ ত্রুটি এবং এর অর্থ সাধারণত দুটি জিনিসের একটি। হয় ZITADEL_DOMAIN সেই নাম নয় যা আপনি ব্রাউজ করছেন, অথবা সামনে থাকা কোনো প্রক্সি Host-কে আপস্ট্রিম ঠিকানায় পরিবর্তন (rewrite) করে দিচ্ছে। সার্ভারের নামের পরিবর্তে IP অ্যাড্রেসে ব্রাউজ করলেও এই ত্রুটি দেখা দেয়।
আপনি এই মানগুলো পরবর্তীতে পরিবর্তন করতে পারবেন। তবে পরিবর্তনটি কার্যকর করতে Zitadel-কে তার সেটআপ ধাপটি পুনরায় চালাতে হয় এবং আপনার নিবন্ধিত প্রতিটি অ্যাপ্লিকেশন তাদের পুরনো redirect URI-গুলোই ধরে রাখে। তাই এখন চূড়ান্ত নামটি বেছে নেওয়া পরবর্তীতে তা পরিবর্তন করার চেয়ে অনেক সহজ।
Let's Encrypt overlay ব্যবহার করে TLS termination
একটি পাবলিক ডোমেইনের জন্য Zitadel-এর Let's Encrypt overlay যোগ করুন। এটি Traefik-কে ACME (automatic certificate management environment) HTTP challenge-এ পরিবর্তন করে এবং প্রকাশিত পোর্টগুলোকে 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-টি API কন্টেইনারে ZITADEL_EXTERNALPORT: 443 এবং ZITADEL_EXTERNALSECURE: true সেট করে, যার ফলে পাবলিক URL এবং Zitadel-এর নিজস্ব তৈরি করা URL-এর মধ্যে মিল থাকে। শুরু করার আগেই A record-টি resolve হতে হবে, কারণ এটি ছাড়া HTTP challenge ব্যর্থ হয়।
আপনি যদি ইতিমধ্যে nginx বা কোনো load balancer-এ TLS termination করে থাকেন, তবে এর পরিবর্তে docker-compose.mode-external-tls.yml ব্যবহার করুন এবং TRAEFIK_TRUSTED_IPS-এ আপনার প্রক্সি থেকে আসা IP রেঞ্জগুলো সেট করুন। Traefik শুধুমাত্র সেই তালিকার ঠিকানাগুলো থেকে আসা X-Forwarded-* হেডার গ্রহণ করে, তাই ভুল মান দিলে forwarded protocol বাতিল হয়ে যায় এবং Zitadel একটি HTTPS সাইটের জন্য http:// URL তৈরি করতে শুরু করে।
একটি upstream প্রক্সির দুটি কাজ থাকে যা Zitadel কঠোরভাবে অনুসরণ করে। এটিকে অবশ্যই backend-এর সাথে HTTP/2 প্রোটোকলে কথা বলতে হবে, কারণ API-টি gRPC। এবং এটিকে অবশ্যই Host হেডারটি X-Forwarded-Proto: https-এর সাথে অপরিবর্তিত অবস্থায় পাস করতে হবে। Zitadel-এর নিজস্ব nginx উদাহরণে এর গঠন দেখানো হয়েছে:
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-এর টেস্ট সেটআপের কন্টেইনার, তাই সেগুলোকে আপনার কন্টেইনারের নাম দিয়ে প্রতিস্থাপন করুন। আপনি যদি 443 ছাড়া অন্য কোনো পোর্টে Zitadel সার্ভ করেন, তবে grpc_set_header Host $host:$server_port; ব্যবহার করুন যাতে পোর্টটি হেডারের সাথে যুক্ত থাকে। বাকি অংশটি একটি সাধারণ virtual host-এর মতো এবং একটি nginx reverse proxy কনফিগারেশনের লাইন-বাই-লাইন ব্যাখ্যা 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 থেকে পড়া হয় না। তাই আপনার নিজস্ব মানগুলো একটি ছোট ওভারলে ফাইলে রাখুন। সেটির নাম দিন 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"আপনি যখন কোনো -f ফ্ল্যাগ ছাড়া Compose চালান, তখন এটি শুধুমাত্র docker-compose.override.yml ফাইলটি লোড করে। 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 ফাইলে এর কারণ পাওয়া যায়। প্রথমবার চালু করার সময় সাধারণত মাস্টার-কি-এর দৈর্ঘ্য বা ডাটাবেস DSN-এর সমস্যার কারণে এমনটি হয়।
https://auth.example.com/ui/console ঠিকানায় লগইন করুন, পাসওয়ার্ড পরিবর্তন করুন এবং অন্য কিছু তৈরি করার আগেই সেই অ্যাকাউন্টের জন্য সেকেন্ড ফ্যাক্টর অথেন্টিকেশন চালু করুন। প্রতিটি ZITADEL_FIRSTINSTANCE_* মান শুধুমাত্র প্রথম ইনস্ট্যান্স তৈরি হওয়ার সময় কার্যকর হয়। একবার ইনস্ট্যান্স তৈরি হয়ে গেলে, এগুলো পরিবর্তন করলে কোনো কাজ হবে না।
কেন SMTP কাজ না করা পর্যন্ত পাসওয়ার্ড রিসেট কার্যকর হয় না
একটি আইডেন্টিটি প্রোভাইডার যদি ইমেইল পাঠাতে না পারে, তবে সেটি এমনভাবে অকেজো হয়ে থাকে যা সপ্তাহের পর সপ্তাহ ধরা পড়ে না। Zitadel ব্যবহারকারীর আমন্ত্রণ, ঠিকানা যাচাইকরণ, পাসওয়ার্ড রিসেট লিঙ্ক, ওয়ান-টাইম কোড এবং ডোমেইন ক্লেইম নোটিশের জন্য ইমেইল পাঠায়। কোনো SMTP প্রোভাইডার কনফিগার করা না থাকলে, কনসোল তবুও কাজটিকে সম্পন্ন হিসেবে দেখায় এবং বার্তাটি একটি নোটিফিকেশন ওয়ার্কারের কাছে চলে যায়, যার কাছে সেটি পাঠানোর কোনো গন্তব্য থাকে না। ডিফল্ট সেটিংস সেই ওয়ার্কারকে MaxAttempts: 3 এবং MaxTtl: 5m প্রদান করে, তাই এটি কয়েক মিনিট ধরে কয়েকবার পুনরায় চেষ্টা করে এবং তারপর থেমে যায়। লিঙ্কের জন্য অপেক্ষারত ব্যক্তিকে কিছুই জানানো হয় না।
কনসোলে https://auth.example.com/ui/console/settings-এ ইনস্ট্যান্স সেটিংসের অধীনে এটি কনফিগার করুন। SMTP প্রোভাইডার ফর্মে প্রেরকের ইমেইল ঠিকানা, প্রেরকের নাম, হোস্ট এবং পোর্ট, ইউজার, একটি SMTP পাসওয়ার্ড এবং একটি TLS টগল চাওয়া হয়। সেভ করার আগে সেই ফর্মের টেস্ট বাটনটি ব্যবহার করুন, কারণ এটি একটি আসল বার্তা পাঠায়: এটি হয় পৌঁছাবে অথবা পৌঁছাবে না।
এনভায়রনমেন্ট ভেরিয়েবলের একটি সংশ্লিষ্ট সেট রয়েছে, ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_HOST এবং এর সহযোগী ভেরিয়েবলগুলো। এগুলো একটি ইনস্ট্যান্স তৈরি করার সময় কার্যকর হয়। ইতিমধ্যে চলমান কোনো স্ট্যাকে এগুলোর কোনো প্রভাব নেই, তাই বিদ্যমান ইনস্ট্যান্সের জন্য কনসোলই সঠিক জায়গা।
VPS থেকে ডেলিভারির ক্ষেত্রে দুটি বিষয় খেয়াল রাখতে হবে, কারণ এখানেই সাধারণত সমস্যা হয়। বেশিরভাগ প্রোভাইডার নতুন অ্যাকাউন্টে আউটবাউন্ড পোর্ট 25 ব্লক করে রাখে, তাই প্রাপকের মেইল সার্ভারে সরাসরি পাঠানোর চেষ্টা কোনো কার্যকর ত্রুটি বার্তা ছাড়াই টাইম-আউট হয়ে যায়। এর পরিবর্তে পোর্ট 587-এ একটি অথেন্টিকেটেড রিলে ব্যবহার করুন। এবং প্রেরক ডোমেইনের জন্য SPF (sender policy framework) এবং DKIM (domainkeys identified mail) রেকর্ড প্রকাশ করুন, অন্যথায় রিসেট লিঙ্কটি স্প্যামে চলে যাবে, যা ব্যবহারকারীর কাছে ইমেইল না পাঠানোর মতোই মনে হবে।
কাউকে আমন্ত্রণ জানানোর আগেই এটি যাচাই করুন। একটি অস্থায়ী ব্যবহারকারী তৈরি করুন, পাসওয়ার্ড রিসেট করার অনুরোধ করুন এবং বার্তাটি পৌঁছাচ্ছে কি না তা দেখুন। যদি না পৌঁছায়, তবে docker compose logs -f zitadel-api SMTP ব্যর্থতার কারণ নির্দেশ করবে। SMTP পাসওয়ার্ড ডাটাবেসে এনক্রিপ্ট করা অবস্থায় সংরক্ষিত থাকে, যা মাস্টারকি আপনার জন্য সুরক্ষিত রাখে।
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 --prunerestic 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_testeventstore স্কিমার টেবিলগুলোর তালিকা দেখা মানে হলো ডাম্পটি সঠিক। যদি কোনো এরর মেসেজ বলে যে স্কিমাটি নেই, তবে বুঝতে হবে ব্যাকআপটি সঠিক নয়। সৌভাগ্যবশত, আপনি এমন দিনে এটি জানতে পেরেছেন যেদিন আপনার কোনো ক্ষতি হয়নি। Compose stack ব্যাকআপ এবং আপগ্রেড করার সাধারণ নিয়ম এখানে প্রায় অপরিবর্তিতভাবে প্রযোজ্য, এবং masterkey-কে একই আর্কাইভের বাইরে রাখাটাই Zitadel-এর জন্য একমাত্র বিশেষ অংশ।
ইনস্ট্যান্সের তথ্য না হারিয়ে Zitadel আপগ্রেড করা
একটি আপগ্রেড হলো .env-এর ভার্সন পরিবর্তন এবং এরপর দুটি কমান্ড চালানো:
docker compose pull
docker compose up -d --waitদ্বিতীয় কমান্ডটি কোনো প্রোডাকশন এনভায়রনমেন্টে চালানোর আগে সেটি কী কাজ করে তা বুঝে নিন। কন্টেইনারের কমান্ড হলো start-from-init, যা সার্ভিস চালু করার আগে init এবং setup ধাপগুলো সম্পন্ন করে। setup ধাপটি মূলত ডাটাবেস মাইগ্রেশনের কাজ করে। অর্থাৎ, ভার্সন পরিবর্তনের পর কন্টেইনার চালু হওয়ার সময় স্বয়ংক্রিয়ভাবে আপনার লাইভ ডাটাবেসে স্কিমা মাইগ্রেশন চলে, আর --wait হেলথচেকের জন্য অপেক্ষা করে। এই কারণেই উপরের রিস্টোর টেস্টটি করা বাধ্যতামূলক।
আপগ্রেড করার ঠিক আগে একটি নতুন ডাটাবেস ডাম্প নিন। গত রাতের ডাম্প এই কাজের জন্য যথেষ্ট নয়।
একবারে বড় কোনো ভার্সন জাম্প করবেন না। v3 থেকে v4-এ যাওয়ার আগে অবশ্যই v3.4.1 বা তার পরবর্তী ভার্সনে থাকতে হবে। কারণ v4-এ পুরনো OIDC সাইনিং কি (signing keys) সরিয়ে ফেলা হয়েছে, ফলে ভার্সন পরিবর্তনের সাথে সাথেই পুরনো কি দিয়ে সাইন করা টোকেনগুলো আর ভেরিফাই হবে না। Zitadel-এর টেকনিক্যাল অ্যাডভাইজরি A-10017-এ এটি বিস্তারিত বলা আছে। এর সমাধান হলো, আপগ্রেড করার আগে পুরনো টোকেনগুলোর মেয়াদ শেষ হওয়ার জন্য নতুন v3 ভার্সনটি কিছুদিন চালিয়ে রাখা।
docker compose logs -f zitadel-api ব্যবহার করে setup ধাপটি পর্যবেক্ষণ করুন। বড় ইভেন্টস্টোরে মাইগ্রেশন সম্পন্ন হতে কয়েক মিনিট সময় লাগতে পারে। হেলথচেক পাস না হওয়া পর্যন্ত Traefik এপিআই-তে কোনো ট্রাফিক পাঠাবে না, তাই এই সময় সাইটটি ডাউন থাকবে। বিষয়টি আগে থেকেই পরিকল্পনা করে রাখুন।
রোলব্যাক মানেই শুধু পুরনো ট্যাগ ব্যবহার করা নয়। একবার মাইগ্রেশন হয়ে গেলে পুরনো বাইনারি নতুন স্কিমা বুঝতে পারে না, তাই রোলব্যাক করার একমাত্র উপায় হলো ডাম্প রিস্টোর করা। যখন আপনার ইনস্ট্যান্সে প্রকৃত ব্যবহারকারী থাকবে, তখন docker-compose.prodlike.yml ব্যবহার করুন। এটি এমন একটি ওভারলে যা init এবং setup ধাপগুলোকে স্টার্টআপ থেকে আলাদা করে দেয়। ফলে মাইগ্রেশন কন্টেইনার রিস্টার্টের একটি পার্শ্বপ্রতিক্রিয়া না হয়ে বরং আপনার নিয়ন্ত্রিত একটি আলাদা ধাপ হিসেবে সম্পন্ন হয়।
আপনার নতুন identity provider-এর দিকে যা নির্দেশ করতে হবে
Console-এ একটি project তৈরি করুন এবং তার ভেতরে একটি application তৈরি করুন। আধুনিক যেকোনো কিছুর জন্য OIDC বেছে নিন, এবং Zitadel আপনাকে একটি client ID, একটি client secret এবং https://auth.example.com/.well-known/openid-configuration-এ একটি discovery document প্রদান করবে। single sign-on সমর্থন করে এমন বেশিরভাগ self-hosted সফটওয়্যার ঠিক এই তথ্যগুলোই চায়।
অনেক সফটওয়্যার এটি সমর্থন করে না, অথবা শুধুমাত্র পেইড টায়ারে এটি সমর্থন করে। প্রথম ক্ষেত্রের জন্য, অ্যাপের সামনে oauth2-proxy যেকোনো HTTP সার্ভিসকে এমন কিছুতে পরিণত করে যা Zitadel সুরক্ষিত রাখতে পারে। দ্বিতীয় ক্ষেত্রের জন্য, আপনি যে ফিচারের জন্য অর্থ প্রদান করেননি তার ওপর ভিত্তি করে কোনো migration পরিকল্পনা করার আগে self-hosted অ্যাপে SSO-এর খরচ বিষয়টি পড়ে নেওয়া বুদ্ধিমানের কাজ।
FAQ
একটি self-hosted Zitadel-এর জন্য কতটুকু RAM এবং CPU প্রয়োজন?
Zitadel-এর প্রোডাকশন গাইড অনুযায়ী, একটি reduced setup-এ একক নোডের জন্য প্রায় 4 CPU কোর এবং 8 GB RAM প্রয়োজন। লগিং এবং মেট্রিক্স চালু থাকলে প্রতি নোডে 16 GB RAM প্রয়োজন হয়। PostgreSQL-এর জন্য আলাদা বাজেট রাখতে হবে; প্রতি 100 রিকোয়েস্ট প্রতি সেকেন্ডের জন্য প্রায় একটি কোর এবং প্রতি কোরের জন্য 4 GB RAM প্রয়োজন। Compose quickstart 2 GB-এর মধ্যে শুরু হয়, যা এটি পরীক্ষা করার জন্য যথেষ্ট, তবে অন্যান্য সার্ভিস এর ওপর নির্ভরশীল হলে প্রজেক্টের সুপারিশকৃত কনফিগারেশন ব্যবহার করা উচিত।
Zitadel masterkey হারিয়ে ফেললে কী হবে?
এর মাধ্যমে এনক্রিপ্ট করা সবকিছু এনক্রিপ্ট করা অবস্থাতেই থাকবে। ক্লায়েন্ট সিক্রেট, আইডেন্টিটি প্রোভাইডার ক্রেডেনশিয়াল, SMTP পাসওয়ার্ড এবং ওয়ান-টাইম-পাসওয়ার্ড সিডগুলো ডিক্রিপ্ট করা সম্ভব হবে না এবং পরবর্তীতে এই কি (key) পরিবর্তন করা যায় না। শুধুমাত্র ডাটাবেস ডাম্প থেকে একটি সচল ইনস্ট্যান্স পুনরুদ্ধার করা সম্ভব নয়, কারণ ডাম্পে শুধুমাত্র সাইফারটেক্সট থাকে এবং কোনো কি (key) থাকে না। মাস্টারকি-টি একটি পাসওয়ার্ড ম্যানেজারে সংরক্ষণ করুন, যা ডাম্প রাখা ব্যাকআপ থেকে আলাদা স্থানে থাকবে। যদি উভয়ই হারিয়ে যায়, তবে নতুন করে ইনস্ট্যান্স তৈরি করাই একমাত্র উপায়।
Zitadel পাসওয়ার্ড রিসেট ইমেইল কেন পৌঁছায় না?
এর কারণ হলো কোনো SMTP প্রোভাইডার কনফিগার করা নেই, অথবা কনফিগার করা প্রোভাইডার ইমেইল পাঠাতে পারছে না। Zitadel প্রতিটি নোটিফিকেশন ডিফল্টভাবে তিনটি প্রচেষ্টার জন্য একটি ওয়ার্কারের কাছে কিউ (queue) করে রাখে এবং কনসোলে সফলতার রিপোর্ট দেখায়, তাই ব্যর্থতাটি নীরবে ঘটে। ইনস্ট্যান্স সেটিংসে SMTP প্রোভাইডার কনফিগার করুন এবং সেই ফর্মে থাকা টেস্ট বাটনটি ব্যবহার করুন, যা একটি আসল মেসেজ পাঠাবে। একটি VPS থেকে পোর্ট 587-এ একটি অথেন্টিকেটেড রিলে ব্যবহার করুন, কারণ অধিকাংশ প্রোভাইডার আউটবাউন্ড পোর্ট 25 ব্লক করে রাখে। এছাড়া সেন্ডিং ডোমেইনের জন্য SPF এবং DKIM রেকর্ড পাবলিশ করুন যাতে ইমেইল স্প্যাম হিসেবে ফিল্টার না হয়।
ইনস্টল করার পর কি Zitadel-এর এক্সটারনাল ডোমেইন পরিবর্তন করা সম্ভব?
হ্যাঁ, তবে শুধুমাত্র .env এডিট করে এটি সম্ভব নয়। ZITADEL_EXTERNALDOMAIN, ZITADEL_EXTERNALPORT এবং ZITADEL_EXTERNALSECURE পরিবর্তন করুন, তারপর Zitadel-কে তার সেটআপ ধাপ পুনরায় চালাতে দিন যাতে এটি পরিবর্তনটি গ্রহণ করতে পারে। আপনার আগে থেকে রেজিস্টার করা অ্যাপ্লিকেশনগুলো তাদের পুরনো রিডাইরেক্ট URI ধরে রাখবে এবং সেগুলোকে হাতে আপডেট করতে হবে। এছাড়া যে রিকোয়েস্টের Host হেডার Zitadel-এর জানা কোনো ডোমেইনের সাথে মেলে না, সেটি Instance not found রেসপন্স পাবে। প্রথমবার শুরু করার আগেই চূড়ান্ত ডোমেইন নাম নির্বাচন করলে এই ঝামেলা এড়ানো যায়।