9.3 KiB
استقرار Production روی Coolify
این پروژه با سه سرویس اجرا میشود:
nginx: فقط سرویسدهنده فایلهای/static/روی پورت 80web: اجرای Django با Gunicorn روی پورت داخلی 8000notification-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
دامنه اصلی HTTPS را در Coolify مستقیماً به سرویس web و پورت 8000 متصل کنید. مقدار Domain سرویس web باید دقیقاً یک URL و بدون فاصله یا کامای انتهایی باشد. Traefik خود Coolify TLS، reverse proxy و هدر X-Forwarded-Proto را مدیریت میکند؛ Nginx جلوی Django قرار نمیگیرد.
فیلد Domain سرویسهای nginx و notification-worker را در UI کاملاً خالی بگذارید. Compose برای Nginx routerهای صریح HTTP/HTTPS با priority برابر 1000 میسازد و /static/، /media/branding/ و /protected-media/ را بدون StripPrefix به پورت 80 میفرستد. hostname این ruleها از ASSET_HOST خوانده میشود. معماری 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
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
- volumeها و database خالی را ایجاد کنید.
- طبق
docs/PRODUCTION_SEED.mdسه فایل seed را داخل volumecustomer_ticket_seedقرار دهید. - Environment Variables را در Coolify وارد کنید.
ASSET_HOSTرا روی hostname عمومی بگذارید، Domain سرویس web را بدون کامای انتهایی تنظیم کنید و Domainهای nginx/worker را خالی نگه دارید؛ سپس Compose را deploy کنید.- log سرویس
webرا برای موفقیت migrate، import production seed، collectstatic و Gunicorn بررسی کنید. وقتی seed موجود است، bootstrap عمداً اجرا نمیشود تا snapshot تغییر نکند. - log سرویس
notification-workerرا بررسی کنید؛ container باید Running بماند. - با 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 مورد اعتماد خود تغییر دهید.