feat: Implement Docker-based deployment for ClinicPro on Coolify
- Added Dockerfile for multi-stage build including PHP, Node.js, and Nginx. - Created docker-compose.coolify.yaml for service orchestration with app, workers, MariaDB, and Redis. - Introduced entrypoint.sh for initialization tasks like JWT key generation and database migrations. - Configured Nginx with default.conf for handling requests and routing to PHP-FPM. - Added php.ini with production settings and opcache configuration. - Set up supervisord.conf to manage PHP-FPM and Nginx processes. - Created frontend-domains.json for managing allowed frontend domains. - Added gen-cors-env.php script to generate CORS environment variables from frontend domains. - Updated framework.yaml to configure trusted proxies and headers. - Created .dockerignore to exclude unnecessary files from the Docker context. - Added .env.coolify.example for environment variable configuration. - Documented deployment steps and troubleshooting in coolify.md.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# دیپلوی ClinicPro روی Coolify (Docker Compose)
|
||||
|
||||
این راهنما نحوهٔ دیپلوی بکاند ClinicPro (Symfony 7.4 + پنل React) را روی **Coolify** با Build Pack از نوع **Docker Compose** توضیح میدهد.
|
||||
|
||||
> همهٔ فایلهای دیپلوی در ریشهٔ ریپو هستند: `Dockerfile`، `docker-compose.coolify.yaml`، پوشهٔ `docker/` و `.env.coolify.example`.
|
||||
> فایلهای `compose.yaml`، `compose.override.yaml` و `.ddev/` مخصوص محیط لوکال (ddev) هستند و **در دیپلوی نقشی ندارند**.
|
||||
|
||||
---
|
||||
|
||||
## معماری دیپلوی
|
||||
|
||||
`docker-compose.coolify.yaml` پنج سرویس بالا میآورد:
|
||||
|
||||
| سرویس | نقش | نکته |
|
||||
|-------|-----|------|
|
||||
| `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
|
||||
|
||||
1. **New Resource → Public/Private Repository** و ریپوی `clinicpro` را انتخاب کن.
|
||||
2. **Build Pack** را روی **Docker Compose** بگذار (نه Nixpacks).
|
||||
3. در تنظیمات:
|
||||
- **Branch:** `main`
|
||||
- **Base Directory:** `/` (ریشهٔ ریپو)
|
||||
- **Docker Compose File:** `docker-compose.coolify.yaml`
|
||||
|
||||
### ۲. اختصاص دامنه
|
||||
|
||||
- در سرویس `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`](../../.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`](../../docker/frontend-domains.json) تولید میشوند. برای افزودن یا حذف یک دامنه:
|
||||
|
||||
```bash
|
||||
# ۱) یک رکورد به آرایهٔ "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) باقی میمانند.
|
||||
|
||||
### ۵. اولین دیپلوی و ساخت ادمین
|
||||
|
||||
1. **Deploy** را بزن. سرویس `app` هنگام استارت بهصورت خودکار:
|
||||
- کلید JWT میسازد (اگر روی volume نباشد)،
|
||||
- کش prod را warm میکند،
|
||||
- migrationها را با `--all-or-nothing` اجرا میکند.
|
||||
2. بعد از سالمشدن سرویسها، از **Terminal** سرویس `app` در Coolify، ادمین اولیه را بساز:
|
||||
```bash
|
||||
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` |
|
||||
Reference in New Issue
Block a user