# دیپلوی ClinicPro (Symfony) روی لیارا با Docker ## پروژه `clinicpro` (Backend Symfony 7.4 + پنل ادمین React، image داکر چندمرحله‌ای). این یک **راهنمای دیپلوی + تسک پیاده‌سازی** است. هدف: انتقال استک فعلی (که برای **Coolify / docker-compose** ساخته شده) به **لیارا**، جایی که docker-compose پشتیبانی نمی‌شود. > منبع: مستندات لیارا — quick-start، deploy-docker-compose، set-envs، use-disk، configure-supercronic. --- ## زمینه پروژه همین الان یک استک داکر کامل دارد که برای Coolify تنظیم شده: - `Dockerfile` — multi-stage (vendor → assets → runtime PHP-FPM + Nginx via Supervisor)، non-root `www-data`، `EXPOSE 8080`، healthcheck روی `/health`. - `docker-compose.yml` — **۳ سرویس**: `app` (وب) + `worker-async` + `worker-scheduler`؛ MariaDB و Redis سرویس‌های جدا (Coolify Database Resources). - `docker/supervisord.conf` — فعلاً **فقط** `php-fpm` و `nginx` را اجرا می‌کند (workerها داخلش نیستند؛ در compose سرویس مجزا بودند). - `docker/entrypoint.sh` — وقتی `RUN_INIT=1`: صبر برای DB → تولید کلید JWT → `cache:clear/warmup` → `doctrine:migrations:migrate`. - `.env.coolify.example` — مرجع کامل متغیرهای محیطی. - دیسک‌های ماندگار (volume) فعلی: `jwt_keys → /app/config/jwt`، `uploads_public → /app/public/uploads`، `uploads_var → /app/var/uploads`. --- ## مشکل / هدف **لیارا از docker-compose مستقیم پشتیبانی نمی‌کند** («لیارا، به صورت مستقیم از Docker Compose پشتیبانی نمی‌کند»). پس ساختار ۳-سرویسی + DB/Redis جداگانه نمی‌تواند عیناً منتقل شود. باید بازچینش شود: | جزء فعلی (compose) | معادل در لیارا | |---|---| | سرویس `app` (PHP-FPM+Nginx) | یک **برنامه داکر** روی لیارا (همین Dockerfile، پورت `8080`) | | `worker-async` + `worker-scheduler` | داخل **همان** image با Supervisor اجرا شوند (توصیه‌شده، تک‌برنامه) — یا برنامه‌های داکر مجزا | | سرویس MariaDB جدا | **دیتابیس مدیریت‌شدهٔ MariaDB لیارا** (شبکهٔ خصوصی) | | سرویس Redis جدا | **Redis مدیریت‌شدهٔ لیارا** (شبکهٔ خصوصی) | | volumeها (`jwt`, `uploads`) | **دیسک‌های لیارا** که در `liara.json` mount می‌شوند | | env tab کولیفای | `liara env set` / کنسول لیارا | **تصمیم معماری (پیش‌فرض توصیه‌شده): تک‌برنامهٔ داکر.** هر دو worker را به `supervisord.conf` اضافه کن تا در همان کانتینر کنار php-fpm/nginx اجرا شوند. ارزان‌ترین و نزدیک‌ترین گزینه به image فعلی. (دلیل اینکه supercronic مناسب نیست: هر دو worker پروسهٔ **بلندمدت** `messenger:consume` هستند نه job دوره‌ای cron — جای آن‌ها Supervisor است نه crontab. supercronic فقط اگر scheduler را به یک دستور one-shot تبدیل کنی به کار می‌آید؛ در «گزینه‌های جایگزین» پایین آمده.) --- ## فایل‌های مرتبط | فایل | نقش | تغییر | |------|-----|------| | `Dockerfile` | image رانتایم | احتمالاً بدون تغییر (سازگار با لیارا است) | | `docker/supervisord.conf` | پروسه‌منیجر کانتینر | **افزودن دو program برای workerها** | | `docker/entrypoint.sh` | init و migration | بازبینی wait-for-DB (بدون compose `depends_on`) | | `liara.json` | پیکربندی دیپلوی لیارا | **فایل جدید — ساخته شود** | | `.env.liara.example` | مرجع env لیارا | **فایل جدید — اختیاری ولی توصیه‌شده** | | `.dockerignore` | استثناهای build | بدون تغییر (`.env` عمداً نگه داشته می‌شود) | --- ## وضعیت فعلی (کد واقعی) `docker/supervisord.conf` فقط دو program دارد: ```ini [program:php-fpm] command=php-fpm -F autorestart=true priority=10 ... [program:nginx] command=nginx -g 'daemon off;' autorestart=true priority=20 ... ``` `docker-compose.yml` workerها را اینطور اجرا می‌کند (همین دستورها باید به supervisord منتقل شوند): ```yaml worker-async: command: php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v worker-scheduler: command: php bin/console messenger:consume scheduler_default --time-limit=3600 -v ``` --- ## وظایف ### ۱. افزودن workerها به Supervisor به انتهای `docker/supervisord.conf` این دو program را اضافه کن. مهم: فقط **یک** پروسه باید init/migration بزند؛ این کار را entrypoint با `RUN_INIT` کنترل می‌کند و چون اینجا تک‌کانتینر است مشکلی نیست (entrypoint قبل از `exec supervisord` یک‌بار اجرا می‌شود، نه per-program). ```ini [program:worker-async] command=php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v autorestart=true priority=30 # مصرف‌کنندهٔ messenger با SIGTERM پیام در حال پردازش را تمام و سپس خارج می‌شود. stopsignal=TERM stopwaitsecs=30 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:worker-scheduler] command=php bin/console messenger:consume scheduler_default --time-limit=3600 -v autorestart=true priority=30 stopsignal=TERM stopwaitsecs=30 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ``` > `--time-limit=3600` باعث می‌شود پروسه هر ساعت سالم خارج شود و Supervisor با `autorestart=true` دوباره بالا بیاورد (جلوگیری از نشت حافظه / اتصال‌های بیات). دقیقاً رفتار compose. ### ۲. ساخت `liara.json` در ریشهٔ پروژه ```json { "app": "clinicpro-api", "platform": "docker", "port": 8080, "healthCheck": { "command": "/usr/local/bin/healthcheck.sh", "interval": 30, "timeout": 5, "initialDelaySeconds": 60 }, "disks": [ { "name": "jwt", "mountTo": "/app/config/jwt" }, { "name": "uploads", "mountTo": "/app/public/uploads" }, { "name": "var-uploads", "mountTo": "/app/var/uploads" } ] } ``` نکات: - `platform: "docker"` → لیارا از `Dockerfile` ریشه build می‌کند. - `port: 8080` چون nginx داخل image روی 8080 (non-root) گوش می‌دهد و `EXPOSE 8080` ست شده. - مسیر mount دیسک‌ها **absolute** و با احتساب `WORKDIR /app` نوشته شده (طبق مستند use-disk). - پیش از دیپلوی، در کنسول لیارا این سه دیسک را با همین نام‌ها بساز: `jwt`، `uploads`، `var-uploads`. - اگر فیلد `healthCheck` در نسخهٔ liara.json پشتیبانی نشد، حذفش کن و healthcheck را از کنسول ست کن؛ خود image هم `HEALTHCHECK` داخلی دارد. ### ۳. ساخت دیتابیس و Redis مدیریت‌شدهٔ لیارا در کنسول لیارا: 1. یک دیتابیس **MariaDB 11.8** بساز (هماهنگ با `serverVersion=mariadb-11.8.0`). شبکهٔ خصوصی را فعال کن. 2. یک **Redis** بساز، شبکهٔ خصوصی فعال. 3. هاست داخلی هرکدام را از صفحهٔ سرویس بردار (روی شبکهٔ خصوصی، مثلاً `clinicpro-db` / `clinicpro-redis`). 4. برنامهٔ داکر و دو سرویس را روی **همان شبکهٔ خصوصی** قرار بده. ### ۴. ست‌کردن متغیرهای محیطی روی لیارا با CLI (`liara env set KEY=VALUE --app clinicpro-api`) یا تب Environment در کنسول. حداقل متغیرهای موردنیاز (از `.env.coolify.example` گرفته شده، فقط هاست‌ها به سرویس‌های لیارا اشاره می‌کنند): ```bash APP_ENV=prod APP_DEBUG=0 APP_SECRET= JWT_PASSPHRASE= # هاست داخلی = نام سرویس دیتابیس لیارا روی شبکهٔ خصوصی DATABASE_URL=mysql://:@:3306/?serverVersion=mariadb-11.8.0&charset=utf8mb4 REDIS_URL=redis://:6379 MESSENGER_TRANSPORT_DSN=redis://:6379/messages JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem APP_BASE_URL=https:// DEFAULT_URI=https:// TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1 ALLOWED_FRONTEND_HOSTS=<از docker/gen-cors-env.php> CORS_ALLOW_ORIGIN=<از docker/gen-cors-env.php> API_IR_BASE_URL=https://s.api.ir API_IR_TOKEN= REFRESH_TOKEN_TTL=2592000 OTP_TTL=1200 MAX_FILE_SIZE_BYTES=5242880 UPLOAD_DIR=var/uploads ``` نکات: - در compose این مقادیر در `x-app-env` بودند؛ روی لیارا چون env-tab وجود دارد همه باید اینجا ست شوند. - `ALLOWED_FRONTEND_HOSTS` و `CORS_ALLOW_ORIGIN` را با اجرای `php docker/gen-cors-env.php` تولید کن (از `docker/frontend-domains.json` می‌خواند). - نیازی به ست‌کردن `RUN_INIT` نیست؛ entrypoint پیش‌فرض `1` می‌گیرد و چون تک‌برنامه است درست است. - کلیدهای SMS و درگاه پرداخت از DB خوانده می‌شوند، نه env. ### ۵. اولین دیپلوی ```bash # نصب CLI (یک‌بار) npm i -g @liara/cli liara login # از ریشهٔ clinicpro: liara deploy --app clinicpro-api --platform docker --port 8080 ``` `liara.json` بیشتر این فلگ‌ها را پوشش می‌دهد، پس `liara deploy` خالی هم کافی است. در اولین بالا آمدن، `entrypoint.sh` صبر می‌کند تا DB جواب دهد، کلید JWT می‌سازد (روی دیسک `jwt` ماندگار)، و migrationها را می‌زند. ### ۶. تأیید سلامت - لاگ‌ها: `liara logs --app clinicpro-api` — باید php-fpm، nginx، و هر دو worker بالا باشند. - `GET https:///health` → `200`. - ورود ادمین در `/admin` و تست یک endpoint تا اتصال DB/Redis تأیید شود. --- ## گزینه‌های جایگزین (در صورت نیاز، نه پیش‌فرض) **الف) workerها به‌صورت برنامهٔ داکر مجزا** (به‌جای افزودن به Supervisor): همین repo را دوبار دیگر با `liara.json` متفاوت دیپلوی کن که `command` را override کند تا فقط `messenger:consume ...` اجرا شود و `RUN_INIT=0` و **بدون** پورت/healthcheck وب. گران‌تر (سه برنامهٔ داکر) ولی ایزوله‌تر. **ب) supercronic برای scheduler:** اگر بخواهی scheduler را به‌جای مصرف‌کنندهٔ بلندمدت، به cron تبدیل کنی: - در `Dockerfile`: ‏`COPY --from=liaracloud/supercronic:v0.1.11 /usr/local/bin/supercronic /usr/local/bin/supercronic` - فایل `crontab` در ریشه: `* * * * * cd /app && php bin/console messenger:consume scheduler_default --limit=1` - یک `[program:supercronic]` در supervisord با `command=supercronic /app/crontab`. - توجه: این الگو فقط برای کارهای **دوره‌ای** مناسب است؛ `worker-async` (SMS) باید مصرف‌کنندهٔ بلندمدت بماند. مگر نیاز خاص، **گزینهٔ پیش‌فرض (Supervisor) را نگه دار.** --- ## نکات مهم - **بدون docker-compose:** هیچ `depends_on`/`service_healthy` روی لیارا نیست؛ منطق wait-for-DB در `entrypoint.sh` (تا ~۶۰s) دقیقاً برای همین لازم است — دست نخورد. - **دیسک نه volume:** اگر روزی `VOLUME` در Dockerfile اضافه شد، طبق مستند لیارا حذفش کن و از دیسک لیارا استفاده کن. الان Dockerfile دستور `VOLUME` ندارد — خوب است. - **ماندگاری کلید JWT:** دیسک `jwt` حتماً mount شود، وگرنه هر دیپلوی کلید نو می‌سازد و همهٔ توکن‌های صادرشده باطل می‌شوند (`--skip-if-exists` فقط وقتی دیسک ماندگار باشد کار می‌کند). - **`.env` عمداً در image است** (به‌خاطر `Dotenv::bootEnv`)؛ مقادیر واقعی از env لیارا می‌آیند و Dotenv متغیر ازقبل‌ست‌شده را بازنویسی نمی‌کند (`clear_env=no` در `docker/php/zz-pool.conf`). این رفتار را خراب نکن. - **هماهنگی نسخهٔ DB:** `serverVersion` در `DATABASE_URL` باید با نسخهٔ دیتابیس مدیریت‌شدهٔ لیارا یکی باشد (11.8). - **پورت تک‌وب:** لیارا فقط یک پورت HTTP بیرونی می‌دهد (8080)؛ MariaDB/Redis فقط روی شبکهٔ خصوصی در دسترس‌اند — درست با معماری ما می‌خواند. - **CORS چنددامنه:** بک‌اند برای ده‌ها دامنهٔ فرانت سرویس می‌دهد؛ بعد از تغییر `docker/frontend-domains.json` دوباره `gen-cors-env.php` بزن و env را به‌روزرسانی کن. - این تغییر فقط زیرساخت دیپلوی است و هیچ endpoint/route را عوض نمی‌کند، پس به‌روزرسانی `docs/api/*` لازم نیست.