10 KiB
دیپلوی ClinicPro روی Coolify (Docker Compose)
این راهنما نحوهٔ دیپلوی بکاند ClinicPro (Symfony 7.4 + پنل React) را روی Coolify با Build Pack از نوع Docker Compose توضیح میدهد.
همهٔ فایلهای دیپلوی در ریشهٔ ریپو هستند:
Dockerfile،docker-compose.yml، پوشهٔdocker/و.env.coolify.example. فایلهایcompose.yaml،compose.override.yamlو.ddev/مخصوص محیط لوکال (ddev) هستند و در دیپلوی نقشی ندارند.
معماری دیپلوی
docker-compose.yml پنج سرویس بالا میآورد:
| سرویس | نقش | نکته |
|---|---|---|
app |
وب (PHP-FPM + Nginx) | دامنه به این سرویس اختصاص مییابد (پورت ۸۰). RUN_INIT=1 → migration و تولید کلید JWT |
worker-async |
مصرف صف async (ارسال SMS) |
RUN_INIT=0 |
worker-scheduler |
مصرف scheduler_default (انقضای نوبتهای پرداختنشده، هر دقیقه) |
RUN_INIT=0 |
mariadb |
دیتابیس MariaDB 11.8 | healthcheck دارد؛ سرویسهای اپ منتظر سالمشدن آن میمانند |
redis |
Messenger transport + کش/OTP | با appendonly persist میشود |
هر سه سرویس اپ از یک image یکسان (همان Dockerfile) ساخته میشوند و فقط command/RUN_INIT آنها متفاوت است.
مراحل دیپلوی
۱. ساخت Resource در Coolify
- New Resource → Public/Private Repository و ریپوی
clinicproرا انتخاب کن. - Build Pack را روی Docker Compose بگذار (نه Nixpacks).
- در تنظیمات:
- Branch:
main - Base Directory:
/(ریشهٔ ریپو) - Docker Compose File:
docker-compose.yml
- Branch:
۲. اختصاص دامنه
- در سرویس
app، همهٔ دامنههایی که باید سرویس بگیرند را وارد کن — هم دامنهٔ API بکاند و هم همهٔ دامنههای فرانتاند (nobat724.comو همهٔ*-nobat.ir). Coolify لیست دامنهٔ کامادار را روی یک سرویس میپذیرد. - چون کانتینر روی پورت
80گوش میدهد، نیازی به افزودن پورت به دامنه نیست. - Coolify بهصورت خودکار برای هر دامنه TLS را از طریق Traefik (Let's Encrypt) صادر میکند.
دو مفهوم را اشتباه نگیر:
- اختصاص دامنه در UI = Traefik برای آن دامنه روت و گواهی TLS میسازد.
CORS_ALLOW_ORIGIN/ALLOWED_FRONTEND_HOSTS= سیمفونی به آن origin اجازهٔ مرورگری/پرداخت میدهد.یک دامنهٔ جدید معمولاً به هر دو نیاز دارد: هم در UI کولیفای اضافه شود، هم در
docker/frontend-domains.json(و سپس بازتولید env). به بخش «چند دامنه فرانتاند» پایین مراجعه کن.
۳. متغیرهای محیطی
محتوای .env.coolify.example را در تب Environment Variables وارد کن. الزامیها پیش از اولین دیپلوی:
| متغیر | توضیح |
|---|---|
APP_SECRET |
php -r "echo bin2hex(random_bytes(32));" |
JWT_PASSPHRASE |
openssl rand -hex 32 — باید قبل از اولین استارت موجود باشد (کلید JWT با آن ساخته میشود) |
DB_PASSWORD |
پسورد یوزر دیتابیس |
DB_ROOT_PASSWORD |
پسورد root مریادیبی |
APP_BASE_URL |
دامنهٔ خودِ بکاند (مثلاً https://api.nobat724.com) — برای callback پرداخت و URL مطلق |
ALLOWED_FRONTEND_HOSTS / CORS_ALLOW_ORIGIN |
دامنههای فرانتاند (چندتایی) — به بخش «چند دامنه» پایین مراجعه کن |
TRUSTED_PROXIES |
پیشفرض رنج شبکهٔ داخلی داکر (در فایل نمونه هست) |
API_IR_TOKEN |
توکن استعلام هویت (خالی = استعلام رد میشود) |
DATABASE_URL،MESSENGER_TRANSPORT_DSNوREDIS_URLدر خودِ compose از نام سرویسها (mariadb/redis) ساخته میشوند؛ در UI تعریف نکن. کلیدهای SMS و درگاه پرداخت از DB («تنظیمات سایت») خوانده میشوند، نه از env. میتوانی بهجای hardcode از magic variableهای Coolify استفاده کنی، مثلاًDB_PASSWORD=${SERVICE_PASSWORD_DB}.
چند دامنه فرانتاند (مهم)
این بکاند به دهها دامنهٔ شهری سرویس میدهد (nobat724.com و *-nobat.ir). دو متغیر باید همهٔ این دامنهها را پوشش دهند:
ALLOWED_FRONTEND_HOSTS— لیست host با کاما؛ در validate کردن host بازگشتِ پرداخت استفاده میشود (تطبیق دقیق درPaymentController::isAllowedFrontend).CORS_ALLOW_ORIGIN— یک regex واحد (nelmio باorigin_regex: true) که فقطhttpsو دقیقاً همان hostها را میپذیرد.
هر دو مقدار بهصورت خودکار از فایل docker/frontend-domains.json تولید میشوند. برای افزودن یا حذف یک دامنه:
# ۱) یک رکورد به آرایهٔ "domains" در docker/frontend-domains.json اضافه/حذف کن، مثلاً:
# { "domain": "newcity-nobat.ir", "label": "شهر جدید" }
# ۲) مقادیر جدید را تولید کن:
ddev exec php docker/gen-cors-env.php # لوکال
# یا روی سرور داخل کانتینر app:
php docker/gen-cors-env.php
# ۳) خروجی (CORS_ALLOW_ORIGIN و ALLOWED_FRONTEND_HOSTS) را در Coolify جایگزین کن و دوباره deploy کن
فیلد
labelفقط برای خوانایی است و در تولید env استفاده نمیشود؛ فقطdomainمهم است.
payment_allowed_frontend_hostsدر «تنظیمات سایت» (DB) بر مقدار env اولویت دارد؛ اگر آن را در DB ست کردهای، آن مرجع است.
۴. Persistent Storage (حیاتی)
این volumeها در compose تعریف شدهاند و Coolify آنها را persist میکند:
| Volume | مسیر | چرا مهم است |
|---|---|---|
jwt_keys |
/app/config/jwt |
مهمترین. کلید JWT بین دیپلویها باید ثابت بماند؛ در غیر این صورت هر دیپلوی همهٔ توکنها را باطل و همهٔ کاربران را logout میکند |
uploads_public |
/app/public/uploads |
فایلهای آپلودی عمومی |
uploads_var |
/app/var/uploads |
فایلهای آپلودی خصوصی |
mariadb_data |
/var/lib/mysql |
دادهٔ دیتابیس |
redis_data |
/data |
پایداری Redis |
مطمئن شو در Coolify این volumeها بهصورت named volume (نه ephemeral) باقی میمانند.
۵. اولین دیپلوی و ساخت ادمین
- Deploy را بزن. سرویس
appهنگام استارت بهصورت خودکار:- کلید JWT میسازد (اگر روی volume نباشد)،
- کش prod را warm میکند،
- migrationها را با
--all-or-nothingاجرا میکند.
- بعد از سالمشدن سرویسها، از Terminal سرویس
appدر Coolify، ادمین اولیه را بساز:php bin/console app:create-admin
نکات عملیاتی
- Workerها: اگر
worker-schedulerبالا نباشد، نوبتهای رزرو ولی پرداختنشده آزاد نمیشوند. اگرworker-asyncبالا نباشد، SMS ارسال نمیشود. هر دو در همین compose مدیریت میشوند و باrestart: unless-stoppedخودکار بازمیگردند. - Migration در دیپلویهای بعدی: فقط سرویس
app(باRUN_INIT=1) migration اجرا میکند تا بین سرویسها race رخ ندهد. هر دیپلوی، migrationهای جدید را اعمال میکند. - Health check: سرویس
appبا یک fsockopen روی پورت 80 سالمبودن خود را گزارش میدهد. - Trusted Proxies: مقدار
TRUSTED_PROXIESبه Symfony میگوید به هدرهایX-Forwarded-*از Traefik اعتماد کند تاhttpsو IP واقعی کلاینت درست تشخیص داده شوند. در محیط لوکال (ddev) این متغیر تنظیم نمیشود و مقدار پیشفرض خالی است. - بدون شبکهٔ سفارشی: طبق توصیهٔ Coolify، در compose هیچ
networks:سفارشی تعریف نشده تا روتینگ Traefik پایدار بماند.
رفع اشکال
| نشانه | علت محتمل | راهحل |
|---|---|---|
| همهٔ کاربران بعد از دیپلوی logout میشوند | volume jwt_keys persist نشده |
بررسی named volume بودن آن |
| خطای اتصال به دیتابیس هنگام استارت | app قبل از سالمشدن mariadb بالا آمده |
depends_on: condition: service_healthy این را پوشش میدهد؛ صبر کن یا لاگ mariadb را ببین |
| پنل admin سفید/بدون استایل | داراییهای public/build ساخته نشده |
بررسی موفقیت stage assets در لاگ build (yarn build) |
| تولید کلید JWT شکست میخورد | JWT_PASSPHRASE تنظیم نشده |
متغیر را در Coolify ست کن و دوباره deploy کن |
| تصاویر/فایلهای آپلودی بعد از ریدیپلوی ناپدید میشوند | volumeهای uploads persist نشده | بررسی uploads_public / uploads_var |