SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-13

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

راهنمای کامل نصب Chatwoot با Docker Compose و Traefik. یاد بگیرید چگونه SMTP را تنظیم کنید، از دیتابیس Postgres بک‌آپ بگیرید و سرویس خود را بدون قطعی به نسخه جدید ارتقا دهید.

آنچه می‌سازید

برای میزبانی Chatwoot روی یک VPS، شما چهار کانتینر را اجرا می‌کنید: یک پردازش وب Rails، یک worker پس‌زمینه Sidekiq، دیتابیس PostgreSQL با افزونه pgvector و Redis. از آنجا که Chatwoot یک میز پشتیبانی مشتری متن‌باز است، شما یک صندوق ورودی تیمی مشترک و یک ویجت چت وب‌سایت روی سروری که کنترل آن را در دست دارید، خواهید داشت. نصب آن حدود 20 دقیقه زمان می‌برد. هر آنچه پس از آن می‌آید، یعنی ارسال ایمیل، پشتیبان‌گیری، ارتقا و تعیین ابعاد، همان چیزی است که مشخص می‌کند آیا این سرویس یک سال بعد همچنان فعال خواهد بود یا خیر.

هر کانتینر یک وظیفه مشخص دارد. Rails داشبورد اپراتور و API (رابط برنامه‌نویسی اپلیکیشن) ویجت را سرویس‌دهی می‌کند. Sidekiq کارهای زمان‌بر را انجام می‌دهد: ارسال ایمیل، بررسی کانال‌های متصل، اجرای قوانین اتوماسیون و تهیه گزارش‌ها. Postgres مکالمات، مخاطبین، حساب‌های اپراتور و تمام تنظیماتی که در داشبورد تغییر می‌دهید را ذخیره می‌کند. Redis صف‌های Sidekiq و کانال pub/sub مربوط به ActionCable را نگه می‌دارد که پیام‌های جدید را بدون نیاز به رفرش صفحه، به داشبورد باز ارسال می‌کند. در اینجا Redis یک کش موقت نیست، زیرا از دست دادن آن به معنای از دست رفتن کارهای در صف است.

ایمیج Postgres در فایل compose اصلی، pgvector/pgvector:pg16 است و نه ایمیج استاندارد postgres، زیرا طرح‌واره (schema) Chatwoot برای ویژگی‌های هوش مصنوعی خود، افزونه vector را فعال می‌کند. اگر از Postgres استاندارد استفاده کنید، اولین اجرای دیتابیس با خطای ERROR: extension "vector" is not available متوقف می‌شود، زیرا فایل کنترل این افزونه در آن ایمیج وجود ندارد. از همان ایمیجی استفاده کنید که در نسخه اصلی ارائه شده است.

این راهنما فرض می‌کند که Docker و یک reverse proxy از قبل روی سرور شما فعال هستند. اگر این‌طور نیست، ابتدا با Docker Compose روی یک VPS شروع کنید و سپس به اینجا بازگردید.

یک سرور مجازی (VPS) برای میزبانی شخصی Chatwoot به چه منابعی نیاز دارد؟

تا اوت 2026، صفحه نیازمندی‌های رسمی، حداقل 4 گیگابایت رم و 4 هسته CPU را برای مدیریت حداکثر 10,000 گفتگو در روز توصیه می‌کند. برای 20,000 گفتگو در روز، 8 گیگابایت رم و 8 هسته CPU پیشنهاد شده است. همچنین حداقل 1 گیگابایت فضای Swap الزامی است؛ دلیل آن مشخص است: جلوگیری از اتمام حافظه در حین فرآیند ارتقا. برای دیتابیس Postgres، پیش از در نظر گرفتن فایل‌های آپلودی، بین 5 تا 10 گیگابایت فضا در نظر بگیرید.

حقیقت ماجرا این است: یک VPS با 2 گیگابایت رم می‌تواند Chatwoot را بالا بیاورد و با دو اپراتور و یک صندوق ورودی خلوت، به‌خوبی کار کند. اما در دو حالت با شکست مواجه می‌شود. اول Sidekiq است که طبق مستندات رسمی، در یک سرور شلوغ بیش از 1 گیگابایت رم مصرف می‌کند؛ بنابراین یک موج ناگهانی ایمیل یا اجرای یک گزارش، پیش از آنکه سهم Rails، Postgres و Redis لحاظ شود، حافظه سرور را پر می‌کند. دوم، فرآیند ارتقا است؛ زیرا db:chatwoot_prepare یک پروسه جدید Rails برای اعمال Migrationها اجرا می‌کند و بالا آمدن Rails در این ایمیج، پیش از انجام هر کار مفیدی، صدها مگابایت رم مصرف می‌کند.

در این شرایط، هیچ هشدار مودبانه‌ای دریافت نمی‌کنید. قابلیت Out of Memory Killer در هسته سیستم‌عامل، سیگنال SIGKILL را به بزرگ‌ترین پروسه می‌فرستد، Docker متوجه مرگ کانتینر می‌شود و restart: always آن را دوباره اجرا می‌کند. سپس docker compose ps کانتینری را نشان می‌دهد که مدام به وضعیت Exited (137) برمی‌گردد، که کد 137 به معنای کشته شدن توسط سیگنال 9 است. این موضوع را با sudo dmesg -T | grep -i "killed process" تأیید کنید؛ این دستور نام پروسه‌ای که توسط هسته انتخاب شده را نمایش می‌دهد.

اگر بودجه شما به 4 گیگابایت رم نمی‌رسد، از یک سرور 2 گیگابایتی با 2 گیگابایت Swap استفاده کنید و بپذیرید که در زمان بار ترافیکی بالا، زمان پاسخ‌دهی کاهش می‌یابد، اما سرویس به‌طور کامل از کار نمی‌افتد. در هر صورت، تعیین سقف سخت‌گیرانه برای حافظه هر سرویس توصیه می‌شود تا یک Worker نتواند دیتابیس را با خود پایین بکشد. به محدودیت‌های حافظه در Docker Compose مراجعه کنید.

فایل‌های آپلودی بخشی هستند که بدون محدودیت تعیین‌شده توسط شما، رشد می‌کنند. هر اسکرین‌شاتی که مشتری پیوست می‌کند در Volume ذخیره‌سازی قرار می‌گیرد و همان‌جا می‌ماند؛ بنابراین به‌جای اینکه فرض کنید دیتابیس دیسک را پر کرده است، docker system df -v را زیر نظر داشته باشید.

دریافت فایل compose و تعیین نسخه (pin)

mkdir -p ~/chatwoot && cd ~/chatwoot
wget -O .env https://raw.githubusercontent.com/chatwoot/chatwoot/develop/.env.example
wget -O docker-compose.yaml https://raw.githubusercontent.com/chatwoot/chatwoot/develop/docker-compose.production.yaml
chmod 600 .env

فایلی که به‌تازگی دانلود کرده‌اید حاوی image: chatwoot/chatwoot:latest است. پیش از انجام هر کار دیگری، آن را تغییر دهید.

services:
  base: &base
    image: chatwoot/chatwoot:v4.16.2
    env_file: .env
    volumes:
      - storage_data:/app/storage

استفاده از latest باعث می‌شود در هر بار اجرای docker compose pull، آخرین نسخه‌ای که همان روز منتشر شده دریافت شود؛ این نسخه ممکن است یک نسخه اصلی (major) با تغییرات ساختاری (migration) باشد که شما از آن بی‌اطلاع هستید. در عمل، تغییرات ساختاری Chatwoot قابل بازگشت نیستند، بنابراین یک به‌روزرسانی ناخواسته به معنای بازگردانی از نسخه پشتیبان است، نه یک عملیات undo ساده. نسخه را تعیین (pin) کنید و تغییر آن را آگاهانه انجام دهید. در اوت 2026، نسخه v4.16.2 آخرین نسخه منتشرشده بود؛ برای اطلاع از نسخه‌ای که باید امروز تعیین کنید، صفحه نسخه‌ها را بررسی کنید.

سرویس base یک لنگر (anchor) در YAML است که rails و sidekiq هر دو آن را ادغام می‌کنند، بنابراین تغییر نسخه در یک مکان، برای هر دو اعمال می‌شود. در حالی که فایل را ویرایش می‌کنید، خط version: '3' را در ابتدای فایل حذف کنید. نسخه‌های جدید Compose این خط را نادیده می‌گیرند و با هر دستور، پیام the attribute 'version' is obsolete, it will be ignored را نمایش می‌دهند.

تکمیل فایل .env

ابتدا مقدار secret را تولید کنید. توسعه‌دهندهٔ اصلی یک مقدار الفبایی-عددی (alphanumeric) توصیه می‌کند، زیرا کاراکترهای خاص هنگام عبور از shell یا parserهای YAML دچار مشکل می‌شوند.

head /dev/urandom | tr -dc A-Za-z0-9 | head -c 63 ; echo ''

سپس این کلیدها را در .env تنظیم کنید.

SECRET_KEY_BASE=<the 63 characters you just generated>
FRONTEND_URL=https://support.example.com
FORCE_SSL=true
DEFAULT_LOCALE=en
ENABLE_ACCOUNT_SIGNUP=true

POSTGRES_HOST=postgres
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=<long random string>
POSTGRES_DATABASE=chatwoot

REDIS_URL=redis://redis:6379
REDIS_PASSWORD=<a different long random string>

RAILS_ENV=production
INSTALLATION_ENV=docker
ACTIVE_STORAGE_SERVICE=local

عبارات POSTGRES_HOST=postgres و redis://redis:6379 نام سرویس‌های Compose هستند که در شبکهٔ پیش‌فرض پروژه resolve می‌شوند. مقدار FRONTEND_URL صرفاً تزئینی نیست. Chatwoot از این مقدار برای ساخت URL اسکریپت ویجت و تمام لینک‌های موجود در ایمیل‌های ارسالی استفاده می‌کند؛ بنابراین مقدار نادرست باعث می‌شود لینک‌های بازنشانی رمز عبور به میزبانی اشاره کنند که پاسخگو نیست.

اکنون به نکتهٔ انحرافی در فایل اصلی توجه کنید. سرویس postgres فایل .env را نمی‌خواند. این سرویس بلوک environment مخصوص به خود را دارد که در آن POSTGRES_PASSWORD= خالی رها شده است؛ بنابراین تنظیم رمز عبور فقط در .env باعث می‌شود دیتابیس بدون رمز عبور باقی بماند در حالی که اپلیکیشن دارای رمز عبور است. سرویس را به همان متغیر ارجاع دهید:

  postgres:
    image: pgvector/pgvector:pg16
    restart: always
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=chatwoot
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

سرویس Compose فایل .env را از دایرکتوری پروژه برای جایگزینی ${...} می‌خواند، بنابراین اکنون هر دو سمت از یک رشتهٔ یکسان استفاده می‌کنند. اگر این بخش را اشتباه تنظیم کنید، Rails با خطای PG::ConnectionBad: FATAL: password authentication failed for user "postgres" متوقف می‌شود.

یک رفتار خاص وجود دارد که تقریباً همه را غافلگیر می‌کند: ایمیج Postgres فقط زمانی POSTGRES_PASSWORD را اعمال می‌کند که یک دایرکتوری دادهٔ خالی را مقداردهی اولیه (initialize) کند. تغییر این مقدار در مراحل بعدی هیچ اثری ندارد، زیرا دستور initdb هرگز برای بار دوم اجرا نمی‌شود. اگر قبلاً stack را یک‌بار اجرا کرده‌اید، باید رمز عبور را مستقیماً داخل دیتابیس تغییر دهید.

docker compose exec postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'the-new-password';"

مقدار ENABLE_ACCOUNT_SIGNUP=true موقتی است. این گزینه فرم ثبت‌نام عمومی را باز می‌کند تا بتوانید اولین حساب کاربری را بسازید. به محض ایجاد حساب کاربری، آن را روی false تنظیم کرده و دوباره docker compose up -d را اجرا کنید؛ در غیر این صورت هر کسی که URL را پیدا کند می‌تواند در سیستم پشتیبانی شما ثبت‌نام کند. پس از آن، ورود کارشناسان از طریق دعوت‌نامه انجام می‌شود و رمزهای عبور آن‌ها فقط در همین اپلیکیشن ذخیره می‌ماند. این وضعیت تا زمانی که تعداد سرویس‌های شما کم است مشکلی ایجاد نمی‌کند، اما وقتی تعداد سرویس‌ها به چندین مورد برسد و از مدیریت لیست‌های کاربری جداگانه خسته شوید، یک سرویس‌دهندهٔ هویت (Identity Provider) خودمیزبان مانند Authentik همان قطعه‌ای است که جایگزین این سیستم می‌شود.

فایل .env اکنون تمام اسرار این stack را به صورت متن ساده (plain text) در خود نگه می‌دارد، بنابراین دسترسی آن را روی حالت 600 تنظیم کنید و آن را در git قرار ندهید. مطلب نحوه خواندن فایل‌های env توسط Compose و محل نشت اسرار به بررسی نکات دقیق، از جمله تفاوت بین env_file و environment می‌پردازد.

قرار دادن Chatwoot پشت Traefik موجود

برای یک برنامه، پروکسی معکوس دوم ایجاد نکنید. اگر Traefik در حال حاضر وظیفه TLS (امنیت لایه انتقال) را برای سایر کانتینرها در این سرور بر عهده دارد، Chatwoot را با یک بلوک label به آن متصل کنید. اگر هنوز چنین تنظیمی ندارید، ابتدا آن را با استفاده از Traefik در مقابل چندین برنامه Docker Compose راه‌اندازی کنید و سپس به اینجا بازگردید.

فایل docker-compose.yaml بالادستی (upstream) را تا حد امکان به حالت پیش‌فرض نزدیک نگه دارید تا بتوانید بعداً آن را با نسخه‌های جدیدتر مقایسه (diff) کنید و تغییرات خود را در یک فایل override اعمال نمایید. Compose به‌طور خودکار docker-compose.override.yaml را ادغام می‌کند و تقسیم Compose در چندین فایل قوانین این ادغام را توضیح می‌دهد.

services:
  rails:
    networks:
      - default
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.chatwoot.rule=Host(`support.example.com`)"
      - "traefik.http.routers.chatwoot.entrypoints=websecure"
      - "traefik.http.routers.chatwoot.tls.certresolver=letsencrypt"
      - "traefik.http.services.chatwoot.loadbalancer.server.port=3000"

networks:
  proxy:
    external: true

از نام‌های entrypoint و certresolver اختصاصی خود استفاده کنید. کانتینر باید در همان شبکه Docker قرار بگیرد که Traefik در آن است؛ این همان کاری است که ورودی proxy انجام می‌دهد. همچنین باید در default نیز باقی بماند، در غیر این صورت دسترسی به Postgres و Redis را از دست می‌دهد. این همان خط دومی است که معمولاً فراموش می‌شود.

بلوک ports: را تغییر ندهید. نسخه بالادستی آن را به 127.0.0.1:3000 متصل می‌کند که فقط loopback است؛ بنابراین از اینترنت قابل دسترس نیست و برای تست از داخل سرور با curl -I http://127.0.0.1:3000 همچنان کاربردی باقی می‌ماند.

داشبورد عامل (agent dashboard) یک اتصال websocket را برای تحویل زنده پیام‌ها به /cable باز نگه می‌دارد. Traefik درخواست ارتقای HTTP را بدون نیاز به پیکربندی اضافی هدایت می‌کند، بنابراین چیزی برای افزودن وجود ندارد. اگر بعداً یک CDN یا پروکسی دیگری را جلوی Traefik قرار دادید، websockets را در آنجا مجاز کنید؛ زیرا نشانه مشکل این است که داشبورد به‌طور عادی بارگذاری می‌شود، اما پیام‌های جدید تنها پس از رفرش دستی ظاهر می‌شوند.

مقداردهی اولیه پایگاه داده و راه‌اندازی پشته

ابتدا سرویس‌های داده را بالا بیاورید و اجازه دهید Postgres اولین اجرای خود را به پایان برساند.

docker compose up -d postgres redis
docker compose logs postgres | tail -n 5

منتظر database system is ready to accept connections بمانید. سپس طرح‌واره (schema) را ایجاد کنید.

docker compose run --rm rails bundle exec rails db:chatwoot_prepare

این دستور در صورت عدم وجود پایگاه داده، آن را ایجاد کرده و سپس طرح‌واره و داده‌های اولیه (seed) را بارگذاری می‌کند. این دستور خطوط مهاجرت (migration) را چاپ کرده و به‌درستی خارج می‌شود. اگر دستور در حال چاپ postgres:5432 - no response متوقف شد، به این معنی است که نقطه ورود (entrypoint) منتظر پایگاه داده‌ای است که هنوز اتصالات را نمی‌پذیرد؛ در اولین اجرا، این معمولاً به این معناست که initdb هنوز در حال کار است. منتظر بمانید، لاگ‌های Postgres را بخوانید و سپس دوباره دستور را اجرا کنید. اگر روی افزونه vector متوقف شد، یعنی شما image مربوط به pgvector را با نسخه معمولی Postgres جایگزین کرده‌اید.

docker compose up -d
docker compose ps
docker compose logs --tail 30 rails

هر چهار کانتینر باید وضعیت Up را نشان دهند و لاگ rails باید با خطی از Puma که روی http://0.0.0.0:3000 گوش می‌دهد، پایان یابد. سپس مسیر عمومی را بررسی کنید:

curl -sI https://support.example.com | head -n 1

وضعیت HTTP/2 200 به این معنی است که کل زنجیره به‌درستی کار می‌کند. خطای 404 از سمت Traefik به این معنی است که قانون مسیریاب (router rule) مطابقت نداشته است که معمولاً به دلیل اشتباه تایپی در نام میزبان (hostname) است. خطای 502 به این معنی است که Traefik مسیریاب را پیدا کرده اما نتوانسته به کانتینر متصل شود؛ این مشکل تقریباً همیشه به دلیل نبود شبکه proxy یا تنظیم نبودن loadbalancer.server.port روی پورت 3000 است.

URL را باز کنید، حساب کاربری خود را در /app/auth/signup ایجاد کنید، سپس ENABLE_ACCOUNT_SIGNUP=false را تنظیم کرده و docker compose up -d را اجرا کنید تا فرم بسته شود.

چرا بازنشانی رمز عبور و گفتگوهای ایمیلی بدون SMTP با شکست مواجه می‌شوند

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

مکانیسم این اتفاق ساده است. بدون تنظیمات SMTP، ActionMailer به صورت پیش‌فرض از تحویل به localhost روی پورت 25 استفاده می‌کند. هیچ سرور ایمیلی درون container مربوط به Rails وجود ندارد، بنابراین job تحویل، خطای Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 25 را صادر می‌کند. ایمیل‌ها از طریق یک background job ارسال می‌شوند، بنابراین این خطا در لاگ Sidekiq ثبت می‌شود و در لاگ Rails دیده نمی‌شود. در همین حال، شخصی که روی "فراموشی رمز عبور" کلیک کرده، پیام تأییدیه خوشحال‌کننده‌ای را می‌بیند اما هیچ ایمیلی دریافت نمی‌کند.

MAILER_SENDER_EMAIL=Support <support@example.com>
SMTP_DOMAIN=example.com
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=support@example.com
SMTP_PASSWORD=<the relay password>
SMTP_AUTHENTICATION=plain
SMTP_ENABLE_STARTTLS_AUTO=true

از پورت 587 با STARTTLS استفاده کنید که اتصال را در حالت متن ساده باز کرده و پیش از احراز هویت، آن را به حالت رمزنگاری‌شده ارتقا می‌دهد. اکثر ارائه‌دهندگان VPS پورت خروجی 25 را برای محدود کردن اسپم مسدود می‌کنند، بنابراین استفاده از یک relay روی پورت 587 معمولاً تنها راهی است که اتصال را برقرار می‌کند. SMTP_DOMAIN دامنه‌ای است که سرور شما در طول مکالمه SMTP اعلام می‌کند و برخی از relayها در صورت عدم تطابق، اتصال را رد می‌کنند.

تنظیمات را اعمال کرده و worker را زیر نظر بگیرید:

docker compose up -d rails sidekiq
docker compose logs -f sidekiq

یک درخواست بازنشانی رمز عبور از صفحه ورود ایجاد کنید. در صورت موفقیت‌آمیز بودن تحویل، job مربوط به mailer در لاگ Sidekiq به صورت عادی پایان می‌یابد. در صورت شکست، کلاس exception نمایش داده می‌شود و سپس Sidekiq با فواصل زمانی افزایشی (backoff) دوباره تلاش می‌کند؛ به همین دلیل است که یک relay معیوب، هر چند دقیقه یک‌بار و برای ساعت‌ها همان خطا را تکرار می‌کند.

دو نوع رد کردن اتصال رایج است و هیچ‌کدام باگ Chatwoot نیستند. 535 Authentication failed به این معنی است که نام کاربری یا رمز عبور برای آن relay اشتباه است؛ بسیاری از ارائه‌دهندگان به جای رمز عبور اصلی حساب کاربری، به یک application password نیاز دارند. 550 Sender address rejected به این معنی است که MAILER_SENDER_EMAIL آدرسی است که relay اجازه ارسال ایمیل از طرف آن را ندارد، بنابراین باید از یک صندوق پستی یا دامنه‌ای استفاده کنید که نزد آن‌ها تأیید شده باشد.

دریافت ایمیل در یک گفتگو، یک job جداگانه است. این کار به MAILER_INBOUND_EMAIL_DOMAIN و RAILS_INBOUND_EMAIL_SERVICE و همچنین یک سرور ایمیل نیاز دارد که پیام‌های ورودی را به Chatwoot تحویل دهد. اجاره یک relay سریع‌ترین راه است. اگر ترجیح می‌دهید کل مسیر ایمیل را خودتان مدیریت کنید، راه‌اندازی سرور ایمیل شخصی با Mailcow توضیح می‌دهد که این تعهد در واقع شامل چه مواردی است.

چه مواردی را پشتیبان‌گیری کنیم و چگونه از صحت بازیابی مطمئن شویم

پشتیبان‌گیری از Chatwoot شامل چهار بخش است و نادیده گرفتن هر یک از آن‌ها، بازیابی را به یک بازسازی کامل تبدیل می‌کند.

  • پایگاه داده Postgres که شامل گفتگوها، مخاطبین، حساب‌های کاربری اپراتورها و تمامی تنظیمات است.
  • ولوم storage_data، زیرا ACTIVE_STORAGE_SERVICE=local فایل‌های آپلود شده را روی دیسک ذخیره می‌کند و در Postgres فقط یک ردیف ارجاع به آن‌ها نگه می‌دارد.
  • فایل .env، زیرا حاوی SECRET_KEY_BASE و کلیدهای ACTIVE_RECORD_ENCRYPTION_* است.
  • فایل‌های compose، زیرا نسخه دقیق image که با طرح پایگاه داده شما مطابقت دارد را ثبت می‌کنند.

اگر فقط پایگاه داده را بازیابی کنید، تمام گفتگوها با پیوست‌های خراب بازمی‌گردند، زیرا ردیف‌ها به فایل‌هایی اشاره می‌کنند که دیگر روی دیسک وجود ندارند.

cd ~/chatwoot
docker compose exec -T postgres pg_dump -U postgres -Fc chatwoot > db-$(date +%F).dump

استفاده از -T اهمیت دارد. بدون آن، Compose یک ترمینال مجازی اختصاص می‌دهد که بایت‌های خط جدید (newline) را در جریان داده بازنویسی می‌کند و در نتیجه فایل dump خروجی توسط pg_restore رد می‌شود. -Fc فرمت اختصاصی است که داده‌ها را فشرده کرده و به pg_restore اجازه می‌دهد به صورت انتخابی عمل کند.

docker run --rm -v chatwoot_storage_data:/data:ro -v "$PWD":/backup alpine \
  tar czf /backup/storage-$(date +%F).tgz -C /data .

نام ولوم برابر است با نام دایرکتوری پروژه شما به اضافه _storage_data. پیش از اعتماد به دستور، آن را با docker volume ls | grep storage_data تأیید کنید، زیرا اگر نامی را وارد کنید که وجود ندارد، Docker به جای خطا دادن، یک ولوم خالی ایجاد می‌کند. در این صورت شما یک آرشیو معتبر اما خالی دریافت می‌کنید و هیچ خطایی هم رخ نمی‌دهد. پس از پایان، حجم فایل را با ls -lh storage-*.tgz بررسی کنید.

هر دو فایل اکنون روی همان دیسکی قرار دارند که از آن محافظت می‌کنند، بنابراین در برابر خرابی دیسک هیچ حفاظتی ندارید. آن‌ها را به خارج از سرور منتقل و رمزنگاری کنید، زیرا dump پایگاه داده شامل تمام پیام‌های مشتریان به صورت متن ساده است. پشتیبان‌گیری رمزنگاری‌شده خارج از سایت با restic به جنبه‌های زمان‌بندی و نگهداری فایل‌ها می‌پردازد.

تمرین بازیابی، آن را پیش از نیاز واقعی انجام دهید

بازیابی را روی یک VPS دوم انجام دهید، نه روی سرور اصلی. فایل .env، فایل‌های compose و هر دو آرشیو را منتقل کنید و سپس دستور زیر را اجرا کنید:

docker compose up -d postgres
docker compose exec -T postgres pg_restore -U postgres -d chatwoot --clean --if-exists < db-2026-08-10.dump
docker run --rm -v chatwoot_storage_data:/data -v "$PWD":/backup alpine \
  sh -c 'rm -rf /data/* && tar xzf /backup/storage-2026-08-10.tgz -C /data'
docker compose up -d

دستور --clean --if-exists اشیاء موجود را پیش از بارگذاری حذف می‌کند، بنابراین آن را فقط روی پایگاه داده‌ای اجرا کنید که از دست دادن آن برایتان اهمیتی ندارد. سپس وارد سیستم شوید و گفتگویی که دارای پیوست است را باز کنید. اگر لیست پیام‌ها بارگذاری شد و فایل دانلود شد، پشتیبان‌گیری شما معتبر است.

بازیابی با SECRET_KEY_BASE متفاوت، تمام کوکی‌های نشست (session cookie) را باطل می‌کند و همه کاربران از سیستم خارج می‌شوند. بازیابی با کلیدهای ACTIVE_RECORD_ENCRYPTION_* متفاوت بدتر است: Chatwoot نمی‌تواند ستون‌های حاوی اعتبارنامه‌های کانال را رمزگشایی کند و خطای ActiveRecord::Encryption::Errors::Decryption را نمایش می‌دهد. به همین دلیل است که .env در لیست پشتیبان‌گیری قرار دارد.

نحوه ارتقای Chatwoot به یک تگ جدید

ترتیب انجام مراحل از خود دستورات اهمیت بیشتری دارد.

  1. یادداشت‌های انتشار (release notes) بین تگ فعلی و تگ مقصد را مطالعه کنید تا از مراحل دستی احتمالی مطلع شوید.
  2. یک نسخه پشتیبان (dump) از دیتابیس و یک آرشیو از فضای ذخیره‌سازی تهیه کنید و مطمئن شوید حجم فایل‌ها منطقی است.
  3. تگ image را برای سرویس base در فایل docker-compose.yaml ویرایش کنید.
  4. ایمیج جدید را pull کنید، استک را متوقف کنید، migrationها را اجرا کنید و سپس دوباره سرویس را استارت بزنید.
docker compose pull
docker compose down
docker compose run --rm rails bundle exec rails db:chatwoot_prepare
docker compose up -d
docker compose images

پیش از اجرای migration، ایمیج جدید را pull کنید؛ زیرا migration باید از داخل ایمیج جدید اجرا شود و ایمیج قدیمی حاوی فایل‌های migration جدید نیست. پیش از اجرای migration، استک را متوقف کنید؛ زیرا کد قدیمی با schema جدید ناسازگار است و اجرای پروسه Rails قدیمی ممکن است باعث بروز خطا یا درج ردیف‌هایی شود که schema جدید آن‌ها را نمی‌پذیرد. متوقف کردن سرویس همچنین حافظه مورد نیاز برای migration را آزاد می‌کند؛ این همان دلیلی است که توسعه‌دهندگان اصلی (upstream) استفاده از swap را توصیه می‌کنند.

دستور docker compose images تگی که هر کانتینر در حال حاضر با آن اجرا می‌شود را نمایش می‌دهد؛ این کار به شما کمک می‌کند تا اگر تگ را ویرایش کرده اما فراموش کرده‌اید ایمیج را pull کنید، متوجه شوید.

به‌طور هم‌زمان از چندین نسخه نپرید. توصیه توسعه‌دهندگان اصلی برای نصب‌های قدیمی این است که مرحله‌به‌مرحله و از طریق تگ‌های میانی ارتقا دهید؛ زیرا migrationها پس از ادغام در schema پایه حذف می‌شوند و ممکن است دیتابیس‌های بسیار قدیمی به وضعیتی برسند که دیگر مسیر مستقیمی برای ارتقا نداشته باشند. هر بار یک نسخه minor را ارتقا دهید و پس از هر مرحله، دستور آماده‌سازی (prepare) را اجرا کنید.

اگر Rails پیش از اجرای migration بالا بیاید، از سرویس‌دهی خودداری کرده و خطای ActiveRecord::PendingMigrationError: Migrations are pending را لاگ می‌کند. اگر restart: always تنظیم شده باشد، کانتینر وارد چرخه ری‌استارت می‌شود و در نتیجه docker compose ps آپ‌تایمی را نشان می‌دهد که هر چند ثانیه یک‌بار ریست می‌شود. با اجرای مرحله آماده‌سازی، این مشکل برطرف می‌شود.

بازگشت به نسخه قبل (Rollback) به معنای بازگرداندن تگ قدیمی و بازیابی نسخه پشتیبان دیتابیس است. هیچ مسیر بازگشت (reverse migration) قابل‌اطمینانی وجود ندارد و به همین دلیل است که مرحله 2 اهمیت حیاتی دارد.

حالت‌های شکست و پیام‌هایی که مشاهده خواهید کرد

خطای 502 Bad Gateway از سمت Traefik. روتر درخواست را تطبیق داده اما backend پاسخی نداده است. دستور docker compose ps را بررسی کنید تا وضعیت rails را به صورت Up ببینید، سپس docker network inspect proxy را اجرا کرده و تأیید کنید که container مربوط به rails در لیست containerها ظاهر می‌شود. containerای که متصل نباشد برای Traefik نامرئی است؛ بنابراین درخواست با روتر تطبیق می‌خورد اما به مقصدی نمی‌رسد.

داشبورد بارگذاری می‌شود اما پیام‌های جدید نیاز به رفرش دارند. اتصال websocket به /cable برقرار نمی‌شود، یا FRONTEND_URL با آدرس موجود در نوار مرورگر مطابقت ندارد. عدم تطابق به این معناست که صفحه تلاش می‌کند یک websocket به مبدأ (origin) متفاوتی باز کند که مرورگر آن را مسدود می‌کند.

FATAL: password authentication failed for user "postgres". رمز عبور در .env با رمز عبوری که در volume داده‌های Postgres ذخیره شده است، تفاوت دارد. این مشکل را با اجرای ALTER USER در داخل container در حال اجرا حل کنید، زیرا ویرایش مجدد .env تغییری در دیتابیسی که قبلاً مقداردهی اولیه شده است، ایجاد نمی‌کند.

NOAUTH Authentication required. سرویس Redis با --requirepass در حال اجراست اما برنامه بدون رمز عبور متصل شده است؛ بنابراین REDIS_PASSWORD در .env وجود ندارد یا اعمال نشده است. آن را مستقیماً با docker compose exec redis redis-cli -a "$REDIS_PASSWORD" ping تست کنید که باید پاسخ PONG را برگرداند.

خروج containerها با کد 137. این کد مربوط به SIGKILL است و در سرورهای کوچک، نشان‌دهنده فعال شدن مکانیزم OOM Killer (خاتمه‌دهنده به دلیل کمبود حافظه) توسط هسته سیستم‌عامل است. فضای swap اضافه کنید، محدودیت حافظه برای هر سرویس تعیین کنید یا به پلن بزرگ‌تری مهاجرت کنید.

FAQ

حداقل رم مورد نیاز برای یک VPS جهت میزبانی Chatwoot چقدر است؟

از اوت 2026، مستندات رسمی حداقل 4 گیگابایت رم و 4 هسته CPU را برای مدیریت تا 10,000 گفتگو در روز پیشنهاد می‌دهند؛ برای 20,000 گفتگو، 8 گیگابایت رم و 8 هسته توصیه می‌شود. حداقل 1 گیگابایت Swap اضافه کنید، زیرا در زمان ارتقا، یک پروسه Rails دوم برای اعمال Migrationها اجرا می‌شود و در این مرحله سرورهای کوچک با کمبود حافظه مواجه می‌شوند. یک VPS با 2 گیگابایت رم بالا می‌آید و برای چند اپراتور کار می‌کند، اما Sidekiq به‌تنهایی تحت فشار می‌تواند بیش از 1 گیگابایت رم مصرف کند؛ بنابراین انتظار داشته باشید که در دوره‌های شلوغ یا هنگام ارتقا، کانتینرها با کد خطای 137 متوقف شوند.

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

به این دلیل که تنظیمات SMTP پیکربندی نشده است؛ در نتیجه ActionMailer تلاش می‌کند ایمیل را به localhost روی پورت 25 ارسال کند، در حالی که هیچ سرور ایمیلی داخل کانتینر وجود ندارد. این عملیات در Sidekiq با خطای Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 25 شکست می‌خورد، اما مرورگر همچنان پیام موفقیت‌آمیز بودن عملیات را نمایش می‌دهد. متغیرهای SMTP_ADDRESS، SMTP_PORT، SMTP_USERNAME، SMTP_PASSWORD و MAILER_SENDER_EMAIL را در .env تنظیم کنید، سرویس‌های rails و sidekiq را ریستارت کنید و سپس هنگام درخواست بازنشانی رمز عبور، docker compose logs -f sidekiq را مانیتور کنید.

برای بازیابی Chatwoot از چه چیزهایی باید نسخه پشتیبان تهیه کنم؟

دیتابیس Postgres، داکر والیوم storage_data، فایل .env و فایل‌های compose. دیتابیس به‌تنهایی کافی نیست، زیرا فایل‌های آپلود شده در والیوم قرار دارند و Postgres فقط ارجاعات به آن‌ها را نگه می‌دارد؛ بنابراین بازیابی دیتابیس به‌تنهایی منجر به گفتگوهایی با پیوست‌های خراب می‌شود. فایل .env اهمیت حیاتی دارد، زیرا تغییر SECRET_KEY_BASE باعث خروج (logout) تمام کاربران می‌شود و تغییر کلیدهای ACTIVE_RECORD_ENCRYPTION_* باعث می‌شود ستون‌های رمزنگاری‌شده غیرقابل خواندن شوند.

چگونه Chatwoot را بدون آسیب به دیتابیس ارتقا دهم؟

ابتدا نسخه پشتیبان تهیه کنید، تگ ایمیج را در فایل compose تغییر دهید و سپس دستورات docker compose pull، docker compose down، docker compose run --rm rails bundle exec rails db:chatwoot_prepare و docker compose up -d را اجرا کنید. ابتدا ایمیج جدید را Pull کنید زیرا Migrationها باید از روی ایمیج جدید اجرا شوند؛ همچنین ابتدا استک را متوقف کنید، زیرا اجرای کدهای قدیمی روی اسکیما جدید باعث بروز خطا می‌شود. در نصب‌های قدیمی، نسخه را یک‌به‌یک ارتقا دهید، زیرا Migrationها پس از ادغام در اسکیما پایه، حذف می‌شوند.

آیا می‌توانم به‌جای pgvector از ایمیج استاندارد postgres استفاده کنم؟

خیر. اسکیما Chatwoot اکستنشن vector را فعال می‌کند، بنابراین ایمیج پیش‌فرض postgres در حین db:chatwoot_prepare با خطای ERROR: extension "vector" is not available مواجه می‌شود، زیرا فایل کنترل این اکستنشن در آن ایمیج موجود نیست. از pgvector/pgvector:pg16 موجود در فایل compose رسمی استفاده کنید یا ایمیج دیگری را به کار بگیرید که شامل pgvector برای نسخه اصلی Postgres شما باشد.

#chatwoot#self-hosting#docker-compose#support-desk#smtp#backups