Files
customerTicket/docs/DEPLOYMENT_COOLIFY.md
T
2026-09-10 11:34:19 +03:30

8.6 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

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

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

دامنه اصلی HTTPS را در Coolify مستقیماً به سرویس web و پورت 8000 متصل کنید. Traefik خود Coolify TLS، reverse proxy و هدر X-Forwarded-Proto را مدیریت می‌کند؛ Nginx جلوی Django قرار نمی‌گیرد.

برای همان دامنه، routeهای /static/، /media/branding/ و /protected-media/ را به سرویس nginx و پورت 80 بدهید و مطمئن شوید routeهای path-based نسبت به route عمومی web اولویت بالاتری دارند. برای notification-worker هیچ domain یا host port عمومی نسازید. اگر نسخه Coolify شما تعریف چند route روی یک دامنه را از UI پشتیبانی نمی‌کند، routing rule متناظر Traefik را برای این سه path به nginx اضافه کنید؛ معماری containerها همچنان بدون proxy_pass بین Nginx و Django است.

فایل‌های آپلودی تیکت عمومی نیستند: 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
MEDIA_SIGNING_SECRET=<separate-random-secret-at-least-32-characters>

نام دیتابیس انتهای 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. Environment Variables را در Coolify وارد کنید.
  3. Compose را deploy، دامنه اصلی را به web:8000 و pathهای static/media گفته‌شده را به nginx:80 متصل کنید.
  4. log سرویس web را برای موفقیت migrate، bootstrap، collectstatic و Gunicorn بررسی کنید.
  5. log سرویس notification-worker را بررسی کنید؛ container باید Running بماند.
  6. یک‌بار superuser بسازید:
docker compose exec web python manage.py createsuperuser
  1. در پنل مدیریت، SMS templateها و NotificationPolicyها را بررسی کنید و با گیرنده آزمایشی مجاز یک 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 مورد اعتماد خود تغییر دهید.