آموزش نصب و میزبانی Supabase روی VPS با Docker
راهنمای کامل اجرای Supabase روی سرور شخصی با Docker Compose. یاد بگیرید چگونه مقادیر فایل .env را امن کنید، 14 سرویس را مدیریت کرده و بدون حذف دادهها، پشته خود را بهروزرسانی کنید.
آنچه در حال ساخت آن هستید
میزبانی شخصی (Self-hosting) سرویس Supabase به معنای اجرای پشته رسمی Docker Compose روی سرور شخصی شماست: شامل Postgres، یک API از نوع REST در مقابل آن، سرویس احراز هویت، فضای ذخیرهسازی فایل، وبسوکتهای بلادرنگ و داشبورد Studio. شما یک مخزن را کلون میکنید، یک فایل .env را ویرایش کرده و حدود 14 کانتینر را اجرا میکنید که در کنار هم مانند یک پروژه Supabase تحت کنترل شما عمل میکنند.
نصب این پشته کوتاه است. بخشی که معمولاً دچار مشکل میشود، فایل .env است. این فایل با مقادیر پیشفرض (secrets) ارائه میشود که در مخزن عمومی منتشر شدهاند؛ بنابراین اگر پشتهای با این مقادیر پیشفرض اجرا شود، برای هر کسی که آن را پیدا کند در دسترس خواهد بود. این راهنما به بررسی مقادیری میپردازد که باید جایگزین کنید، هدف هر سرویس چیست، این پشته واقعاً به چه مقدار حافظه نیاز دارد و چگونه میتوانید بدون حذف پایگاه داده، آن را بهروزرسانی کنید.
اگر با Docker Compose آشنا نیستید، ابتدا اصول Docker Compose روی VPS را مطالعه کنید. تمام موارد زیر فرض را بر این میگذارند که دستور docker compose version هماکنون یک نسخه را نمایش میدهد.
محتوای واقعی این پشته (stack)
Supabase یک برنامه واحد نیست. فایل Compose مجموعهای از سرویسهای مجزا را روی یک شبکه راهاندازی میکند. شناخت هر یک از این سرویسها باعث میشود بهجای مواجهه با لیستی از نامهای کانتینر، ساختاری داشته باشید که بتوانید آن را عیبیابی کنید.
dbهمان PostgreSQL به همراه افزونههای Supabase است. تمام سرویسهای دیگر با آن در ارتباط هستند. اگر این کانتینر در وضعیت ناسالم (unhealthy) باشد، سایر سرویسها نیز از کار میافتند.kongدروازه (gateway) API است. این سرویس روی پورت 8000 گوش میدهد و درخواستهای/rest/v1/،/auth/v1/و/storage/v1/را به backend مناسب هدایت میکند. این تنها کانتینری است که باید در معرض اینترنت قرار دهید.restهمان PostgREST است. این سرویس طرحواره (schema) دیتابیس Postgres شما را میخواند و آن را به عنوان یک REST API ارائه میدهد؛ بنابراین یک جدول جدید بدون نیاز به کدنویسی، به یک endpoint جدید تبدیل میشود.authهمان GoTrue است. این سرویس توکنهای وب JSON (JWT) را صادر میکند که کاربران شما را احراز هویت میکنند.storageوimgproxyمدیریت آپلود فایل و تغییر اندازه تصاویر را بر عهده دارند.realtimeتغییرات دیتابیس را از طریق websocket استریم میکند.studioوmetaداشبورد و API مدیریتی پشت آن هستند.analytics(Logflare) وvectorلاگها را جمعآوری میکنند وsupavisorمدیریتکننده اتصال (connection pooler) دیتابیس Postgres است.
این لیست دلیل اصلی مقادیر منابعی است که در ادامه ذکر شده است. شما فقط یک دیتابیس را اجرا نمیکنید؛ بلکه در حال اجرای یک دیتابیس به همراه دهها سرویس پشتیبان هستید.
تخمین منابع: در نظر گرفتن 8 گیگابایت رم
این پشته نرمافزاری در تاریخ جولای 2026، در یک نصب تازه و پیش از اضافه شدن دادهها یا ترافیک شما، حدود 2.5 تا 3 گیگابایت از حافظه مقیم (resident memory) را اشغال میکند. سرویس تحلیل (analytics) و پردازش Node.js در Studio، دو مصرفکننده اصلی منابع هستند. یک سرور 2 گیگابایتی کانتینرها را اجرا میکند، اما بلافاصله یکی از آنها توسط قابلیت OOM Killer هسته سیستمعامل متوقف میشود (معمولاً analytics یا db)؛ نشانه این وضعیت، گیر کردن کانتینر در حلقه راهاندازی مجدد با کد خروج 137 است.
برای هر سرویسی که به آن وابستگی دارید، 8 گیگابایت رم و 4 هسته مجازی پردازنده (vCPU) اختصاص دهید. 4 گیگابایت رم برای یک نمونه توسعه شخصی کافی است، به شرطی که بپذیرید اجرای همزمان یک کوئری سنگین و کار با محیط Studio با کندی همراه خواهد بود. فضای دیسک نیز اهمیت دارد، زیرا Postgres، حجم ذخیرهسازی (storage volume) و دادههای لاگ همگی در دایرکتوری پروژه قرار دارند. با 40 گیگابایت شروع کنید و میزان مصرف را زیر نظر داشته باشید. شمارش سرویسها پیش از انتخاب پلن سرور، عادتی است که برای هر سرویس self-hosted باید رعایت کنید، چرا که PhotoPrism و Immich حداقل نیازهای رم واقعی دارند که بسیار فراتر از مقادیر ذکر شده در صفحات راهاندازی سریع آنهاست.
نصب: کلون کردن مخزن رسمی
مسیر پشتیبانیشده، دایرکتوری docker را از مخزن اصلی به دایرکتوری پروژه شخصی شما کپی میکند. این جداسازی اهمیت دارد، زیرا به این معنی است که یک git pull در آینده نمیتواند .env شما را بازنویسی کند.
git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pullدستور docker compose pull چندین گیگابایت تصویر را دانلود میکند. این عملیات باید با علامتگذاری تمام سرویسها به عنوان Pulled به پایان برسد. خطای manifest unknown در اینجا به این معنی است که تگ تصویر پینشده در منبع اصلی حذف شده است؛ راه حل این است که به جای ویرایش دستی تگها، نسخه جدیدتری از مخزن را دریافت (pull) کنید.
رازهایی که باید پیش از اولین اجرا تغییر دهید
این کار را پیش از راهاندازی stack انجام دهید، نه پس از آن. چندین مورد از این مقادیر در اولین بوت در دادهها نوشته میشوند، بنابراین تغییر آنها در مراحل بعدی به معنای بازنشانی (reset) پایگاه داده است.
مخزن شامل یک تولیدکننده (generator) است که تمام مقادیر را بهدرستی ایجاد میکند، از جمله دو کلید API که باید با JWT secret جدید شما امضا شوند.
sh utils/generate-keys.sh --update-envاین اسکریپت مقادیر جدیدی برای JWT_SECRET، ANON_KEY، SERVICE_ROLE_KEY، SECRET_KEY_BASE، REALTIME_DB_ENC_KEY، VAULT_ENC_KEY، PG_META_CRYPTO_KEY و توکنهای Logflare در .env مینویسد. این اسکریپت به openssl نیاز دارد که در هر ایمیج استاندارد Ubuntu موجود است.
دو مقداری که توسط اسکریپت تنظیم نمیشوند و باید بهصورت دستی در .env ویرایش کنید:
POSTGRES_PASSWORD. فقط از حروف و اعداد استفاده کنید. علائم نگارشی در اینجا باعث خرابی رشتههای اتصال (connection strings) میشود که چندین سرویس با ترکیب رشتهها میسازند؛ این خطا بهصورت خطای احراز هویت ظاهر میشود، نه خطای تجزیه (parsing)، که باعث میشود کاربران به دنبال علت در جای اشتباهی بگردند.DASHBOARD_USERNAMEوDASHBOARD_PASSWORD. اینها اعتبارنامههای احراز هویت پایه برای Studio هستند. رمز عبور پیشفرض ارائهشده دقیقاًthis_password_is_insecure_and_should_be_updatedاست.
درک کنید که چرا ANON_KEY و SERVICE_ROLE_KEY را نمیتوان بهصورت دستی ساخت. هر دو JWTهایی هستند که با JWT_SECRET امضا شدهاند. درگاه (gateway) این امضا را در هر درخواست بررسی میکند، بنابراین کلیدی که با secret شما مطابقت نداشته باشد با {"message":"Invalid authentication credentials"} رد میشود. این رایجترین خطای self-hosting است: اپراتور JWT_SECRET را تغییر داده اما کلیدهای دمو را نگه داشته است. همیشه هر سه را با هم تولید کنید.
با SERVICE_ROLE_KEY مانند رمز عبور root رفتار کنید. این مقدار امنیت در سطح ردیف (row level security) را بهطور کامل دور میزند. این مقدار فقط باید در کد سمت سرور قرار گیرد و نه جای دیگر.
مقادیر SITE_URL و API_EXTERNAL_URL را روی آدرسی تنظیم کنید که کاربران واقعاً به آن دسترسی خواهند داشت، برای مثال https://supabase.example.com. سرویس Auth لینکهای تأیید ایمیل و callbackهای OAuth را بر اساس این مقادیر میسازد، بنابراین باقی گذاشتن آنها روی http://localhost:8000 باعث میشود تمام کاربران شما به ماشین خودشان هدایت شوند.
سپس آنچه را که دارید بررسی کنید:
sh run.sh secretsسرویس را اجرا کرده و از سلامت آن اطمینان حاصل کنید
sh run.sh start
docker compose psrun.sh start دستور docker compose up -d --wait را اجرا میکند، بنابراین تا زمانی که بررسیهای سلامت (health checks) با موفقیت انجام نشوند، دستور به پایان نمیرسد. هر سرویس باید وضعیت running (healthy) یا running را نشان دهد. اولین بوت بین 2 تا 4 دقیقه زمان میبرد، زیرا Postgres پیش از آنکه هر سرویس دیگری بتواند متصل شود، اسکریپتهای اولیه خود را اجرا میکند.
اگر یک کانتینر مدام در حال restart شدن است، لاگهای آن را بر اساس نام سرویس بخوانید:
docker compose logs db
docker compose logs authسرویس Studio سپس روی پورت 8000 در دسترس خواهد بود و از شما نام کاربری و رمز عبوری که برای داشبورد تنظیم کردهاید را درخواست میکند.
پورت 8000 را روی اینترنت عمومی قرار ندهید
Kong روی پورت 8000 از پروتکل HTTP ساده استفاده میکند. تمام کلیدهای API و رمزهای عبور کاربران بهصورت متن آشکار (clear text) در شبکه منتقل میشوند و اعتبارنامههای Studio نیز از نوع basic authentication هستند که در واقع کدگذاری base64 است و نه رمزنگاری.
یک reverse proxy در مقابل آن قرار دهید، TLS (امنیت لایه انتقال) را در آنجا خاتمه دهید و Kong را به آدرس loopback متصل (bind) کنید تا هیچ منبع دیگری نتواند به آن دسترسی داشته باشد. در docker-compose.yml نگاشت پورت kong به 127.0.0.1:8000:8000 تغییر مییابد و پروکسی درخواستها را به آن مقصد هدایت میکند. بخش استفاده از Traefik در مقابل چندین برنامه Compose جنبههای مربوط به گواهیها را پوشش میدهد.
سایر پورتها را نیز در فایروال ببندید، زیرا Docker با نوشتن قوانین iptables اختصاصی خود، پورتها را منتشر میکند که تنظیمات ساده ufw آنها را نادیده میگیرند. این تله در چرا کانتینرهای Docker قوانین ufw شما را نادیده میگیرند توضیح داده شده است.
از پایگاه داده نسخه پشتیبان تهیه کنید، نه از دایرکتوری
دادههای Postgres در یک bind mount در مسیر ./volumes/db/data قرار دارند. کپی کردن این دایرکتوری در حالی که container در حال اجراست، منجر به یک نسخه ناقص (torn copy) میشود؛ زیرا Postgres عملیات نوشتن را در حافظه موقت (buffer) نگه میدارد و فایلهای روی دیسک تنها در لحظه checkpoint دارای ثبات هستند. بازیابی چنین نسخهای معمولاً انجام میشود، اما گاهی اوقات آخرین تراکنشها را بدون هیچ هشداری از دست میدهد که بدترین حالت ممکن برای یک نسخه پشتیبان است.
به جای آن از dump استفاده کنید. دستور pg_dumpall در داخل container اجرا میشود و یک snapshot با ثبات ایجاد میکند:
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sqlپیش از آنکه به فایل اعتماد کنید، بررسی کنید که خالی نباشد. سپس این dumpها را طبق یک زمانبندی مشخص از سرور خارج کنید؛ این همان کاری است که پشتیبانگیری رمزنگاریشده خارج از سایت با restic برای آن طراحی شده است. همزمان از .env خود نیز نسخه پشتیبان تهیه کنید. از دست دادن JWT_SECRET به این معنی است که تمام توکنهای صادر شده نامعتبر میشوند و تمام اسرار رمزنگاریشده ذخیرهشده غیرقابل خواندن خواهند بود.
فایلهای آپلود شده در ./volumes/storage قرار دارند و چون فایلهای معمولی هستند، کپی کردن ساده آنها مشکلی ندارد.
بهروزرسانی بدون از دست دادن دادهها
Supabase نسخههای image را در docker-compose.yml ثابت میکند؛ بنابراین تا زمانی که خودتان آن را تغییر ندهید، هیچچیز جابهجا نمیشود. ثابتکردن نسخه، روشی ارزشمند برای الگوبرداری در هر stack است که بهصورت دستی assemble میکنید؛ به همین دلیل یک relay خودمیزبان RustDesk دو image سرور خود را ثابت میکند و از tag متغیر پیروی نمیکند. upgrade باید کاری باشد که در صبحی با زمان کافی برای آن انتخاب میکنید. هر بار ابتدا یک dump بگیرید.
docker compose pull
sh run.sh recreateدستور recreate استک را متوقف کرده و دوباره با imageهای جدید اجرا میکند. دادههای شما حفظ میشوند، زیرا در bind mountهای روی host قرار دارند و نه داخل containerها. پیش از ارتقای نسخه اصلی (major version)، حتماً CHANGELOG.md را در مخزن مطالعه کنید، چرا که ارتقای نسخه اصلی Postgres خودکار نیست و نیازمند dump و restore است.
برای اعمال تغییرات در خود فایل Compose، مخزن upstream را دوباره clone کنید و دایرکتوری docker آن را روی پروژه خود کپی کنید؛ مراقب باشید که فایل .env بازنویسی نشود.
بازنشانی کامل (full reset) که همه چیز از جمله پایگاه داده را حذف میکند، یک اسکریپت جداگانه است و پیش از اجرا درخواست تأیید میکند:
sh reset.shFAQ
چرا فراخوانیهای API من خطای "Invalid authentication credentials" را برمیگردانند؟
مقدار ANON_KEY یا SERVICE_ROLE_KEY شما با JWT_SECRET که در حال حاضر در .env قرار دارد، امضا نشده است. گیتوی امضای هر درخواست را بررسی میکند و در صورت عدم تطابق، آن را رد میکند. هر سه مورد را با استفاده از sh utils/generate-keys.sh --update-env دوباره تولید کنید، سپس sh run.sh recreate را اجرا کنید تا سرویسها مقادیر جدید را بخوانند.
آیا میتوانم Supabase خود-میزبانیشده را روی یک VPS با 2 گیگابایت رم اجرا کنم؟
خیر، قابل اطمینان نیست. این استک از ژوئیه 2026 در حالت بیکار نزدیک به 3 گیگابایت رم مصرف میکند، زیرا حدود چهارده سرویس را اجرا میکند. بنابراین یک سرور 2 گیگابایتی باعث میشود کانتینرها توسط out of memory killer متوقف شوند و شما کد خروج 137 را در docker compose ps مشاهده خواهید کرد. برای محیط عملیاتی از 8 گیگابایت رم استفاده کنید و 4 گیگابایت را حداقل مقدار برای توسعه شخصی در نظر بگیرید.
آیا Supabase خود-میزبانیشده شامل edge functions میشود؟
بله. فایل Compose شامل runtime مبتنی بر Deno برای توابع است و هر چیزی را که در مسیر ./volumes/functions قرار دهید، اجرا میکند. این مورد شامل شبکه توزیع جهانی پلتفرم ابری نیست، بنابراین توابع شما فقط روی همان یک سرور و در یک موقعیت جغرافیایی اجرا میشوند.
چگونه مستقیماً به دیتابیس Postgres متصل شوم؟
برای دسترسی به shell تعاملی روی خود سرور از docker exec -it supabase-db psql -U postgres استفاده کنید. برای کلاینتهای خارجی، از طریق Supavisor روی پورت 5432 با کاربر postgres.<POOLER_TENANT_ID> و POSTGRES_PASSWORD خود متصل شوید. این پورت را برای اینترنت عمومی باز نکنید. از طریق VPN یا تونل SSH به آن دسترسی پیدا کنید.
چرا لینک ایمیلهای تأیید احراز هویت من به localhost اشاره میکنند؟
مقادیر SITE_URL و API_EXTERNAL_URL در .env روی حالت پیشفرض باقی ماندهاند. سرویس احراز هویت، تمام لینکهای تأیید و بازنشانی رمز عبور را بر اساس این دو مقدار میسازد، بنابراین آدرسی را ارسال میکند که به آن دستور داده شده است. هر دو را روی URL عمومی واقعی خود تنظیم کنید و استک را دوباره ایجاد نمایید.