Files
customerTicket/docs/DEPLOYMENT_COOLIFY.md
T
2026-09-11 13:32:31 +03:30

10 KiB

استقرار Production روی Coolify

این پروژه با سه سرویس اجرا می‌شود:

  • nginx: فقط سرویس‌دهنده فایل‌های /static/ روی پورت 80
  • web: اجرای Django با Gunicorn روی پورت داخلی 8000
  • notification-worker: اجرای دائمی python manage.py process_notifications برای SMS و Bale

PostgreSQL جزو این Compose نیست و از دیتابیس مدیریت‌شده موجود استفاده می‌شود. Supervisor لازم نیست؛ هر process یک container مستقل دارد و restart: unless-stopped بازیابی آن را انجام می‌دهد.

1. Volumeهای Coolify

پیش از اولین deploy سه Docker volume با همین نام‌ها ایجاد کنید:

customer_ticket_static
customer_ticket_media
customer_ticket_seed

در صورت انتخاب نام دیگر، متغیرهای STATIC_VOLUME_NAME، MEDIA_VOLUME_NAME و SEED_VOLUME_NAME را مطابق آن تنظیم کنید. media حاوی آپلودهای ماندگار است. static در هر startup دوباره با collectstatic تکمیل می‌شود اما برای اشتراک بین web و Nginx volume لازم دارد. سه فایل تولیدشده در production-seed/ باید پیش از اولین startup داخل ریشه volume سوم قرار گیرند.

این Compose هر سه سرویس را به شبکه خارجی coolify متصل می‌کند تا hostname داخلی PostgreSQL نصب‌شده در Coolify از داخل web و notification-worker قابل resolve باشد. شبکه باید از قبل روی سرور Coolify موجود باشد؛ Compose به دلیل external: true آن را ایجاد نمی‌کند.

2. دامنه‌ها و Traefik خود Coolify

این راهنما برای حالت عادی Git-based Docker Compose Application است، نه گزینه Raw Compose Deployment. در این حالت Coolify باید routeهای Traefik را از فیلدهای Domains بسازد؛ routerهای کامل Traefik را به‌صورت دستی داخل Compose تعریف نکنید.

در Configuration > General مقدارها را دقیقاً به شکل زیر وارد کنید:

  • Domains for web: https://connect.loolegostar.com:8000
  • Domains for notification-worker: خالی
  • Domains for nginx: https://connect.loolegostar.com/static,https://connect.loolegostar.com/media/branding,https://connect.loolegostar.com/protected-media

در Configuration > Advanced گزینه Strip Prefixes را خاموش کنید. در غیر این صورت Coolify پیشوند /static یا /media/branding را قبل از تحویل درخواست حذف می‌کند، در حالی که docker/nginx.conf عمداً همان مسیرهای کامل را match می‌کند. بعد از Save، گزینه Reload Compose File و سپس Redeploy را اجرا کنید.

Traefik خود Coolify ruleهای Host/PathPrefix، TLS و reverse proxy را تولید می‌کند. به‌دلیل باگ رقابت route مسیرمحور با route عمومی در برخی نسخه‌های Coolify، Compose روی routerهای تولیدشده‌ی nginx صراحتاً priority=10000، یک service روی پورت 80 و شبکه‌ی resource-specific را تنظیم می‌کند. ترتیب سه Domain بالا را تغییر ندهید، چون شماره routerهای 0 تا 2 از همین ترتیب ساخته می‌شود. Nginx جلوی Django قرار نمی‌گیرد و فقط فایل‌های static و media را سرو می‌کند.

فایل‌های آپلودی تیکت عمومی نیستند: Django ابتدا مجوز را بررسی می‌کند و سپس کاربر را با یک URL امضاشده پنج‌دقیقه‌ای به /protected-media/ روی Nginx می‌فرستد. branding عمومی زیر /media/branding/ مستقیماً از Nginx سرو می‌شود.

3. متغیرهای ضروری

مقادیر زیر را در Environment Variables برنامه Coolify بسازید:

DJANGO_SECRET_KEY=<random-secret-at-least-50-characters>
DJANGO_ALLOWED_HOSTS=support.example.com
CSRF_TRUSTED_ORIGINS=https://support.example.com
PUBLIC_BASE_URL=https://support.example.com
WEBOTP_HOST=support.example.com
TIME_ZONE=Asia/Tehran

DATABASE_URL=postgresql://postgres:<URL_ENCODED_PASSWORD>@<INTERNAL_HOST>:5432/customerTicket
DB_CONN_MAX_AGE=60
DB_CONNECT_TIMEOUT=10
DB_SSLMODE=prefer
DATABASE_WAIT_TIMEOUT=60

STATIC_VOLUME_NAME=customer_ticket_static
MEDIA_VOLUME_NAME=customer_ticket_media
SEED_VOLUME_NAME=customer_ticket_seed
MEDIA_SIGNING_SECRET=<separate-random-secret-at-least-32-characters>
REQUIRE_PRODUCTION_SEED=1

نام دیتابیس انتهای URL باید نام واقعی دیتابیس باشد. اگر دیتابیس ساخته‌شده customerTicket است، URL باید به /customerTicket ختم شود؛ URLی که به /postgres ختم می‌شود به دیتابیس پیش‌فرض postgres متصل خواهد شد. اگر password شامل @، :، /، # یا % است آن را URL-encode کنید.

می‌توانید به‌جای DATABASE_URL آن را خالی بگذارید و POSTGRES_DB، POSTGRES_USER، POSTGRES_PASSWORD، POSTGRES_HOST و POSTGRES_PORT را جداگانه تعریف کنید. یکی از این دو روش را انتخاب کنید.

4. تنظیم SMS

فقط provider مورد استفاده را انتخاب و secretهای همان provider را پر کنید.

0098SMS

SMS_PROVIDER_CLASS=apps.integrations.sms.sms0098.ZeroZeroNinetyEightSMSProvider
SMS0098_USERNAME=<username>
SMS0098_PASSWORD=<password>
SMS0098_FROM=<sender-number>
SMS0098_API_URL=http://www.0098sms.com/sendsmslink.aspx

Behin SMS

SMS_PROVIDER_CLASS=apps.integrations.sms.behin.BehinSMSProvider
BEHIN_SMS_USERNAME=<username>
BEHIN_SMS_PASSWORD=<password>
BEHIN_SMS_SPECIAL_NUMBER=<sender-number>
BEHIN_SMS_WSDL_URL=http://panel.behinsms.com/SMSWS/SOAP.asmx?wsdl

HTTP JSON provider

SMS_PROVIDER_CLASS=apps.integrations.sms.http.HTTPSMSProvider
SMS_API_URL=https://provider.example/api/messages
SMS_API_TOKEN=<token>
SMS_SENDER=<sender>

5. تنظیم Bale و worker

BALE_API_BASE=https://tapi.bale.ai
BALE_BOT_TOKEN=<bot-token>
BALE_GROUP_CHAT_ID=<group-chat-id>
INTEGRATION_HTTP_TIMEOUT_SECONDS=10
MAX_NOTIFICATION_ATTEMPTS=5

در production نبودن BALE_BOT_TOKEN خطای قابل‌مشاهده ایجاد می‌کند و job را بی‌صدا SENT علامت نمی‌زند. worker هر پنج ثانیه outbox دیتابیس را بررسی می‌کند، با قفل skip_locked از پردازش هم‌زمان جلوگیری می‌کند و خطاها را با backoff تا سقف MAX_NOTIFICATION_ATTEMPTS دوباره امتحان می‌کند.

سیاست تأخیر پیامک پاسخ کارشناس و اعلان پاسخ مشتری در Bale از مدل NotificationPolicy در پنل مدیریت دیتابیس کنترل می‌شود؛ متغیر قدیمی STAFF_REPLY_SMS_THROTTLE_SECONDS مرجع اصلی این رفتار نیست.

6. تنظیمات امنیتی

SECURE_SSL_REDIRECT=true
SECURE_HSTS_SECONDS=31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS=true
SECURE_HSTS_PRELOAD=false

SECURE_HSTS_PRELOAD را فقط وقتی true کنید که تمام subdomainها همیشه HTTPS هستند. DJANGO_ALLOWED_HOSTS فقط hostname و CSRF_TRUSTED_ORIGINS origin کامل همراه https:// است. برای چند مقدار از کاما استفاده کنید.

7. ترتیب اولین deploy

  1. volumeها و database خالی را ایجاد کنید.
  2. طبق docs/PRODUCTION_SEED.md سه فایل seed را داخل volume customer_ticket_seed قرار دهید.
  3. Environment Variables را در Coolify وارد کنید.
  4. Domain سرویس web و سه Domain مسیرمحور nginx را مطابق بخش ۲ وارد، Strip Prefixes را خاموش و Domain سرویس worker را خالی نگه دارید؛ سپس Compose را deploy کنید.
  5. log سرویس web را برای موفقیت migrate، import production seed، collectstatic و Gunicorn بررسی کنید. وقتی seed موجود است، bootstrap عمداً اجرا نمی‌شود تا snapshot تغییر نکند.
  6. log سرویس notification-worker را بررسی کنید؛ container باید Running بماند.
  7. با superuser منتقل‌شده از SQLite وارد شوید و با گیرنده آزمایشی مجاز یک SMS و یک پیام Bale واقعی بفرستید.

8. کنترل سلامت و عیب‌یابی

  • GET /healthz/ هم web server و هم اتصال دیتابیس را بررسی می‌کند.
  • docker compose ps باید web و nginx را healthy و worker را Running نشان دهد.
  • docker compose logs --tail=200 web notification-worker nginx خطاهای startup و provider را نمایش می‌دهد.
  • وضعیت outbox را می‌توان در Django admin یا shell از مدل NotificationJob بررسی کرد؛ FAILED و last_error دلیل خطا را نگه می‌دارند.
  • برای پردازش کنترل‌شده یک job مشخص: python manage.py process_notifications --once --job-id <ID>.

ارسال اعلان ماهیت at-least-once دارد: اگر provider پیام را بپذیرد ولی process پیش از ثبت نتیجه در PostgreSQL قطع شود، retry ممکن است پیام تکراری بسازد. dedupe_key جلوی ساخت job تکراری در برنامه را می‌گیرد، اما تضمین exactly-once به پشتیبانی idempotency از طرف provider نیاز دارد.

9. Mirrorهای ایرانی

Dockerfile به‌طور پیش‌فرض PyPI لیارا را از آدرس https://package-mirror.liara.ir/repository/pypi/simple و mirror Debian لیارا را استفاده می‌کند. اگر mirror Debian در محیط شما متفاوت است، build argument با نام DEBIAN_MIRROR_HOST را تغییر دهید. برای image مربوط به Nginx نیز می‌توانید NGINX_IMAGE را به مسیر registry mirror مورد اعتماد خود تغییر دهید.